/*-------------------------------------------------------------------------
 *
 * execExpr.c
 *	  表达式评估基础设施。
 *
 *	在执行器启动期间，我们将每个表达式树（之前已由解析器和规划器处理）编译成
 *	一个 ExprState，使用 ExecInitExpr() 等。这将树转换为一个平坦的
 *	ExprEvalSteps 数组，可以看作程序中的指令。
 *	在运行时，我们将执行步骤，从第一个开始，直到达到 EEOP_DONE 操作码。
 *
 *	此文件包含“编译”逻辑。它独立于我们使用的特定执行技术（switch 语句、计算跳转、
 *	JIT 编译等）。
 *
 *	有关一些背景信息，请参阅 src/backend/executor/README，特别是
 *	“表达式树和 ExprState 节点”、“表达式初始化”和“表达式评估”部分。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/execExpr.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/nbtree.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_type.h"
#include "executor/execExpr.h"
#include "executor/nodeSubplan.h"
#include "funcapi.h"
#include "jit/jit.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/subscripting.h"
#include "optimizer/optimizer.h"
#include "pgstat.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"


typedef struct ExprSetupInfo
{
	/* 从inner/outer/scan元组槽获取的最高属性编号： */
	AttrNumber	last_inner;
	AttrNumber	last_outer;
	AttrNumber	last_scan;
	/* 在表达式中出现的MULTIEXPR子计划节点： */
	List	   *multiexpr_subplans;
} ExprSetupInfo;

static void fc_ExecReadyExpr(ExprState *fc_state);
static void fc_ExecInitExprRec(Expr *fc_node, ExprState *fc_state,
							Datum *fc_resv, bool *fc_resnull);
static void fc_ExecInitFunc(ExprEvalStep *fc_scratch, Expr *fc_node, List *fc_args,
						 Oid fc_funcid, Oid fc_inputcollid,
						 ExprState *fc_state);
static void fc_ExecCreateExprSetupSteps(ExprState *fc_state, Node *fc_node);
static void fc_ExecPushExprSetupSteps(ExprState *fc_state, ExprSetupInfo *fc_info);
static bool fc_expr_setup_walker(Node *fc_node, ExprSetupInfo *fc_info);
static bool fc_ExecComputeSlotInfo(ExprState *fc_state, ExprEvalStep *fc_op);
static void fc_ExecInitWholeRowVar(ExprEvalStep *fc_scratch, Var *fc_variable,
								ExprState *fc_state);
static void fc_ExecInitSubscriptingRef(ExprEvalStep *fc_scratch,
									SubscriptingRef *fc_sbsref,
									ExprState *fc_state,
									Datum *fc_resv, bool *fc_resnull);
static bool fc_isAssignmentIndirectionExpr(Expr *fc_expr);
static void fc_ExecInitCoerceToDomain(ExprEvalStep *fc_scratch, CoerceToDomain *fc_ctest,
								   ExprState *fc_state,
								   Datum *fc_resv, bool *fc_resnull);
static void fc_ExecBuildAggTransCall(ExprState *fc_state, AggState *fc_aggstate,
								  ExprEvalStep *fc_scratch,
								  FunctionCallInfo fcinfo, AggStatePerTrans fc_pertrans,
								  int fc_transno, int fc_setno, int fc_setoff, bool fc_ishash,
								  bool fc_nullcheck);



/*
 * ExecInitExpr: 准备一个表达式树以供执行
 *
 * 该函数构建并返回一个实现给定Expr节点树的ExprState。返回的ExprState可以传递给ExecEvalExpr以进行执行。由于Expr树对于ExecInitExpr和ExecEvalExpr来说是只读的，因此可以同时多次执行同一计划树。 （但请注意，ExprState确实在运行时发生变化，因此不能并发重用。）
 *
 * 这必须在一个会持续到表达式重复执行所需的内存上下文中调用。通常，该上下文将与相关ExprContext的每个查询上下文相同。
 *
 * 树中发现的任何Aggref、WindowFunc或SubPlan节点都将添加到父PlanState持有的此类节点列表中。
 *
 * 注意：没有ExecEndExpr函数；我们假设任何资源清理工作将通过释放构建状态树的内存上下文来处理。需要额外清理工作的函数可以在ExprContext中注册关闭回调。
 *
 *	'node'是要编译的表达式树的根。
 *	'parent'是拥有该表达式的PlanState节点。
 *
 * 如果'parent'为NULL，则表示我们正在准备一个与计划树无关的表达式。 （如果是这样，它不能有聚合或子计划。）这种情况通常应该通过ExecPrepareExpr来处理，而不是直接在这里处理。
 *
 * 此外，如果'node'为NULL，我们只返回NULL。这对于某些可能有也可能没有需要编译的表达式的调用者是方便的。请注意，NULL的ExprState指针*不能*传递给ExecEvalExpr，尽管ExecQual和ExecCheck将接受一个（并将其视为“真”）。
 */
ExprState * ExecInitExpr(Expr *fc_node, PlanState *fc_parent)
{
	ExprState  *fc_state;
	ExprEvalStep fc_scratch = {0};

	/* 特殊情况：NULL表达式产生一个NULL的ExprState指针 */
	if (fc_node == NULL)
		return NULL;

	/* 用空步骤列表初始化ExprState */
	fc_state = makeNode(ExprState);
	fc_state->expr = fc_node;
	fc_state->parent = fc_parent;
	fc_state->ext_params = NULL;

	/* 根据需要插入设置步骤 */
	fc_ExecCreateExprSetupSteps(fc_state, (Node *) fc_node);

	/* 编译表达式本身 */
	fc_ExecInitExprRec(fc_node, fc_state, &fc_state->resvalue, &fc_state->resnull);

	/* 最后，附加一个DONE步 */
	fc_scratch.opcode = EEOP_DONE;
	ExprEvalPushStep(fc_state, &fc_scratch);

	fc_ExecReadyExpr(fc_state);

	return fc_state;
}

/*
 * ExecInitExprWithParams: 准备一个独立的表达式树以供执行
 *
 * 这与ExecInitExpr相同，只是没有父PlanState，而是我们可以有一个描述PARAM_EXTERN参数的ParamListInfo。
 */
ExprState * ExecInitExprWithParams(Expr *fc_node, ParamListInfo fc_ext_params)
{
	ExprState  *fc_state;
	ExprEvalStep fc_scratch = {0};

	/* 特殊情况：NULL表达式产生一个NULL的ExprState指针 */
	if (fc_node == NULL)
		return NULL;

	/* 用空步骤列表初始化ExprState */
	fc_state = makeNode(ExprState);
	fc_state->expr = fc_node;
	fc_state->parent = NULL;
	fc_state->ext_params = fc_ext_params;

	/* 根据需要插入设置步骤 */
	fc_ExecCreateExprSetupSteps(fc_state, (Node *) fc_node);

	/* 编译表达式本身 */
	fc_ExecInitExprRec(fc_node, fc_state, &fc_state->resvalue, &fc_state->resnull);

	/* 最后，附加一个DONE步 */
	fc_scratch.opcode = EEOP_DONE;
	ExprEvalPushStep(fc_state, &fc_scratch);

	fc_ExecReadyExpr(fc_state);

	return fc_state;
}

/*
 * ExecInitQual: 准备一个qual以供ExecQual执行
 *
 * 准备评估一个联合布尔表达式（带有隐式 AND 语义的qual列表），如果没有子表达式为假，则返回真。
 *
 * 如果列表为空，我们必须返回真。由于这是一个非常常见的情况，我们进一步优化，将其转换为NULL的ExprState指针，而不是设置一个计算常量TRUE的ExprState。（一些特别热点的ExecQual调用者检测到这一点并完全避免调用ExecQual。）
 *
 * 如果任何子表达式返回NULL，则合取的结果为假。这使得ExecQual主要用于评估WHERE子句，因为SQL规定具有NULL WHERE结果的元组将不被选择。
 */
ExprState * ExecInitQual(List *fc_qual, PlanState *fc_parent)
{
	ExprState  *fc_state;
	ExprEvalStep fc_scratch = {0};
	List	   *fc_adjust_jumps = NIL;
	ListCell   *fc_lc;

	/* 短路（在此和ExecQual中）以处理空限制列表 */
	if (fc_qual == NIL)
		return NULL;

	Assert(IsA(fc_qual, List));

	fc_state = makeNode(ExprState);
	fc_state->expr = (Expr *) fc_qual;
	fc_state->parent = fc_parent;
	fc_state->ext_params = NULL;

	/* 将表达式标记为与 ExecQual() 一起使用 */
	fc_state->flags = EEO_FLAG_IS_QUAL;

	/* 根据需要插入设置步骤 */
	fc_ExecCreateExprSetupSteps(fc_state, (Node *) fc_qual);

	/*
	 * ExecQual() 需要在返回 NULL 的表达式时返回 false。这使我们能够在第一次遇到 NULL 时短路评估。由于 qual 评估是一个热点路径，因此值得使用一个比 BOOL_AND 更简单的用于 qual 评估的特殊操作码（后者具有更复杂的 NULL 处理）。
	 */
	fc_scratch.opcode = EEOP_QUAL;

	/*
	 * 我们可以使用 ExprState 的 resvalue/resnull 作为每个 qual 表达式的目标。
	 */
	fc_scratch.resvalue = &fc_state->resvalue;
	fc_scratch.resnull = &fc_state->resnull;

	foreach(fc_lc, fc_qual)
	{
		Expr	   *fc_node = (Expr *) lfirst(fc_lc);

		/* 首先评估表达式 */
		fc_ExecInitExprRec(fc_node, fc_state, &fc_state->resvalue, &fc_state->resnull);

		/* 然后发出 EEOP_QUAL 以检测其是否为 false（或 null） */
		fc_scratch.d.qualexpr.jumpdone = -1;
		ExprEvalPushStep(fc_state, &fc_scratch);
		fc_adjust_jumps = lappend_int(fc_adjust_jumps,
								   fc_state->steps_len - 1);
	}

	/* 调整跳转目标 */
	foreach(fc_lc, fc_adjust_jumps)
	{
		ExprEvalStep *fc_as = &fc_state->steps[lfirst_int(fc_lc)];

		Assert(fc_as->opcode == EEOP_QUAL);
		Assert(fc_as->d.qualexpr.jumpdone == -1);
		fc_as->d.qualexpr.jumpdone = fc_state->steps_len;
	}

	/*
	 * 最后，我们不需要做更多的事情。最后一个 qual 表达式必须返回 TRUE，并且由于其结果存储在所需的输出位置，我们就完成了。
	 */
	fc_scratch.opcode = EEOP_DONE;
	ExprEvalPushStep(fc_state, &fc_scratch);

	fc_ExecReadyExpr(fc_state);

	return fc_state;
}

/*
 * ExecInitCheck：为 ExecCheck 执行准备检查约束
 *
 * 这与 ExecInitQual/ExecQual 非常相似，除了结合的 null 结果被视为 TRUE。这种行为适用于评估 CHECK 约束，因为 SQL 指定 NULL 约束条件不算失败。
 *
 * 请注意，像 ExecInitQual 一样，这期望输入为隐式 AND 格式。具有正常显式 AND 格式表达式的 ExecCheck 用户可以简单地应用 ExecInitExpr 以生成适合 ExecCheck 的输入。
 */
ExprState * ExecInitCheck(List *fc_qual, PlanState *fc_parent)
{
	/* 对于空限制列表进行短路（在这里和 ExecCheck 中） */
	if (fc_qual == NIL)
		return NULL;

	Assert(IsA(fc_qual, List));

	/*
	 * 只需将隐式 AND 列表转换为显式 AND（如果有多个条目），并正常编译。与 ExecQual 不同，我们不能在 NULL 结果上短路，因此需要常规的 AND 行为。
	 */
	return ExecInitExpr(make_ands_explicit(fc_qual), fc_parent);
}

/*
 * 对表达式列表调用 ExecInitExpr()，返回 ExprStates 列表。
 */
List * ExecInitExprList(List *fc_nodes, PlanState *fc_parent)
{
	List	   *fc_result = NIL;
	ListCell   *fc_lc;

	foreach(fc_lc, fc_nodes)
	{
		Expr	   *fc_e = lfirst(fc_lc);

		fc_result = lappend(fc_result, ExecInitExpr(fc_e, fc_parent));
	}

	return fc_result;
}

/*
 *		ExecBuildProjectionInfo
 *
 * 为在给定的 econtext 中评估给定的 tlist 并将结果存储到元组槽中构建一个 ProjectionInfo 节点。（调用者必须确保元组槽有一个与 tlist 匹配的描述符！）
 *
 * inputDesc 可以为 NULL，但如果它不是 NULL，我们会检查 tlist 中的简单 Var 是否与描述符匹配。为关系扫描计划节点提供 inputDesc 是重要的，因为这可以交叉检查关系自计划创建以来是否没有改变。在计划的更高层次上，不需要重新检查。
 *
 * 这是通过内部构建一个 ExprState 来实现的，该 ExprState 一次性执行整个投影。
 *
 * 注意：在 PG v10 之前，targetList 是 ExprStates 的列表；现在它应该是规划器创建的 targetlist，因为我们在这里进行编译。
 */
ProjectionInfo *
ExecBuildProjectionInfo(List *fc_targetList,
						ExprContext *fc_econtext,
						TupleTableSlot *fc_slot,
						PlanState *fc_parent,
						TupleDesc fc_inputDesc)
{
	ProjectionInfo *fc_projInfo = makeNode(ProjectionInfo);
	ExprState  *fc_state;
	ExprEvalStep fc_scratch = {0};
	ListCell   *fc_lc;

	fc_projInfo->pi_exprContext = fc_econtext;
	/* 我们将 ExprState 嵌入到 ProjectionInfo 中，而不是进行额外的 palloc */
	fc_projInfo->pi_state.type = T_ExprState;
	fc_state = &fc_projInfo->pi_state;
	fc_state->expr = (Expr *) fc_targetList;
	fc_state->parent = fc_parent;
	fc_state->ext_params = NULL;

	fc_state->resultslot = fc_slot;

	/* 根据需要插入设置步骤 */
	fc_ExecCreateExprSetupSteps(fc_state, (Node *) fc_targetList);

	/* 现在编译每个 tlist 列 */
	foreach(fc_lc, fc_targetList)
	{
		TargetEntry *fc_tle = lfirst_node(TargetEntry, fc_lc);
		Var		   *fc_variable = NULL;
		AttrNumber	fc_attnum = 0;
		bool		fc_isSafeVar = false;

		/*
		 * 如果 tlist 表达式是安全的非系统 Var，则使用快速路径 ASSIGN_*_VAR 操作码。"安全" 意味着我们不需要在计划启动期间应用 CheckVarSlotCompatibility()。如果提供了源槽，则在这里进行等效测试；如果没有提供槽，则我们假设不需要检查，因为我们处理的是非关系扫描级表达式。
		 */
		if (fc_tle->expr != NULL &&
			IsA(fc_tle->expr, Var) &&
			((Var *) fc_tle->expr)->varattno > 0)
		{
			/* 非系统 Var，但到底有多安全？ */
			fc_variable = (Var *) fc_tle->expr;
			fc_attnum = fc_variable->varattno;

			if (fc_inputDesc == NULL)
				fc_isSafeVar = true;	/* 无法检查，只能假设安全 */
			else if (fc_attnum <= fc_inputDesc->natts)
			{
				Form_pg_attribute fc_attr = TupleDescAttr(fc_inputDesc, fc_attnum - 1);

				/*
				 * 如果用户属性被删除或具有类型不匹配，则不使用 ASSIGN_*_VAR。相反，让常规表达式机制处理（这可能会出错）。
				 */
				if (!fc_attr->attisdropped && fc_variable->vartype == fc_attr->atttypid)
				{
					fc_isSafeVar = true;
				}
			}
		}

		if (fc_isSafeVar)
		{
			/* 快速路径：只需生成一个 EEOP_ASSIGN_*_VAR 步骤 */
			switch (fc_variable->varno)
			{
				case INNER_VAR:
					/* 从内部节点获取元组 */
					fc_scratch.opcode = EEOP_ASSIGN_INNER_VAR;
					break;

				case OUTER_VAR:
					/* 从外部节点获取元组 */
					fc_scratch.opcode = EEOP_ASSIGN_OUTER_VAR;
					break;

					/* INDEX_VAR 由默认情况处理 */

				default:
					/* 从正在扫描的关系中获取元组 */
					fc_scratch.opcode = EEOP_ASSIGN_SCAN_VAR;
					break;
			}

			fc_scratch.d.assign_var.attnum = fc_attnum - 1;
			fc_scratch.d.assign_var.resultnum = fc_tle->resno - 1;
			ExprEvalPushStep(fc_state, &fc_scratch);
		}
		else
		{
			/*
			 * 否则，正常编译列表达式。
			 *
			 * 我们不能让表达式直接评估到结果槽中，因为结果槽（以及 exprstate）在执行之间可能会发生变化。我们相反地评估到 ExprState 的 resvalue/resnull 中，然后再移动。
			 */
			fc_ExecInitExprRec(fc_tle->expr, fc_state,
							&fc_state->resvalue, &fc_state->resnull);

			/*
			 * 列可能在上层节点中被多次引用，因此强制值为只读 - 但只有在它可能是扩展数据时。
			 */
			if (get_typlen(exprType((Node *) fc_tle->expr)) == -1)
				fc_scratch.opcode = EEOP_ASSIGN_TMP_MAKE_RO;
			else
				fc_scratch.opcode = EEOP_ASSIGN_TMP;
			fc_scratch.d.assign_tmp.resultnum = fc_tle->resno - 1;
			ExprEvalPushStep(fc_state, &fc_scratch);
		}
	}

	fc_scratch.opcode = EEOP_DONE;
	ExprEvalPushStep(fc_state, &fc_scratch);

	fc_ExecReadyExpr(fc_state);

	return fc_projInfo;
}


/*
 *		ExecBuildUpdateProjection
 *
 * 为在 UPDATE 过程中构建一个 ProjectionInfo 节点以构造新元组。 
 * 投影将在给定的 econtext 中执行，并且结果将存储在给定的元组槽中。 
 * （调用者必须确保元组槽具有与目标关系匹配的描述符！）
 *
 * 当 evalTargetList 为假时，targetList 包含已由子计划节点计算的 UPDATE ... SET 表达式；来自该 tlist 的值假设可在“外部”元组槽中可用。
 * 当 evalTargetList 为真时，targetList 包含必须计算的 UPDATE ... SET 表达式（这可能包含对外部、内部或扫描元组槽的引用）。
 *
 * 在任何一种情况下，targetColnos 包含与 targetList 中非 resjunk 条目对应的目标列号列表。 
 * tlist 的值分配到结果元组槽的这些列中。 
 * 在 targetColnos 中未列出的目标列的值将从 UPDATE 的旧元组中填充，假设在“扫描”元组槽中可用。
 *
 * targetList 还可以包含 resjunk 列。 
 * 如果 evalTargetList 为真，这些列必须被评估，但它们的值会被丢弃。
 *
 * relDesc 必须描述我们打算更新的关系。
 *
 * 这基本上是 ExecBuildProjectionInfo 的一种专用变体。
 * 然而，它还执行等效于 ExecCheckPlanOutput 的安全检查。
 * 由于我们从不创建等同于要分配的整个元组的正规 tlist，因此没有方便的方法来应用
 * ExecCheckPlanOutput，因此我们必须在这里进行安全检查。
 */
ProjectionInfo *
ExecBuildUpdateProjection(List *fc_targetList,
						  bool fc_evalTargetList,
						  List *fc_targetColnos,
						  TupleDesc fc_relDesc,
						  ExprContext *fc_econtext,
						  TupleTableSlot *fc_slot,
						  PlanState *fc_parent)
{
	ProjectionInfo *fc_projInfo = makeNode(ProjectionInfo);
	ExprState  *fc_state;
	int			fc_nAssignableCols;
	bool		fc_sawJunk;
	Bitmapset  *fc_assignedCols;
	ExprSetupInfo fc_deform = {0, 0, 0, NIL};
	ExprEvalStep fc_scratch = {0};
	int			fc_outerattnum;
	ListCell   *fc_lc,
			   *fc_lc2;

	fc_projInfo->pi_exprContext = fc_econtext;
	/* 我们将 ExprState 嵌入到 ProjectionInfo 中，而不是进行额外的 palloc */
	fc_projInfo->pi_state.type = T_ExprState;
	fc_state = &fc_projInfo->pi_state;
	if (fc_evalTargetList)
		fc_state->expr = (Expr *) fc_targetList;
	else
		fc_state->expr = NULL;		/* 未使用 */
	fc_state->parent = fc_parent;
	fc_state->ext_params = NULL;

	fc_state->resultslot = fc_slot;

	/*
	 * 检查 targetList 以查看有多少非垃圾列，并验证非垃圾列在垃圾列之前。
	 */
	fc_nAssignableCols = 0;
	fc_sawJunk = false;
	foreach(fc_lc, fc_targetList)
	{
		TargetEntry *fc_tle = lfirst_node(TargetEntry, fc_lc);

		if (fc_tle->resjunk)
			fc_sawJunk = true;
		else
		{
			if (fc_sawJunk)
				elog(ERROR, "subplan target list is out of order");
			fc_nAssignableCols++;
		}
	}

	/* 每个非垃圾列应该有一个 targetColnos 条目 */
	if (fc_nAssignableCols != list_length(fc_targetColnos))
		elog(ERROR, "targetColnos does not match subplan target list");

	/*
	 * 构建 targetColnos 中列的位图集。 
	 * （我们可以只使用 list_member_int() 测试，但这会在许多列的情况下导致 O(N^2) 的表现。）
	 */
	fc_assignedCols = NULL;
	foreach(fc_lc, fc_targetColnos)
	{
		AttrNumber	fc_targetattnum = lfirst_int(fc_lc);

		fc_assignedCols = bms_add_member(fc_assignedCols, fc_targetattnum);
	}

	/*
	 * 我们需要插入 EEOP_*_FETCHSOME 步骤以确保输入元组被充分解构。
	 * 扫描元组必须至少解构到我们需要的最后一列。
	 */
	for (int fc_attnum = fc_relDesc->natts; fc_attnum > 0; fc_attnum--)
	{
		Form_pg_attribute fc_attr = TupleDescAttr(fc_relDesc, fc_attnum - 1);

		if (fc_attr->attisdropped)
			continue;
		if (bms_is_member(fc_attnum, fc_assignedCols))
			continue;
		fc_deform.last_scan = fc_attnum;
		break;
	}

	/*
	 * 如果我们实际上在评估 tlist，则还要 incorporar 它的输入
	 * 要求；否则，我们只需获取 "外部" 元组的适当列数。
	 */
	if (fc_evalTargetList)
		fc_expr_setup_walker((Node *) fc_targetList, &fc_deform);
	else
		fc_deform.last_outer = fc_nAssignableCols;

	fc_ExecPushExprSetupSteps(fc_state, &fc_deform);

	/*
	 * 现在生成代码以评估 tlist 的可分配表达式或
	 * 从外部元组中获取它们，同时验证它们将
	 * 是正确的数据类型。以上检查确保 forboth()
	 * 将准确地迭代非垃圾列。请注意，我们不
	 * 费心评估任何剩余的 resjunk 列。
	 */
	fc_outerattnum = 0;
	forboth(fc_lc, fc_targetList, fc_lc2, fc_targetColnos)
	{
		TargetEntry *fc_tle = lfirst_node(TargetEntry, fc_lc);
		AttrNumber	fc_targetattnum = lfirst_int(fc_lc2);
		Form_pg_attribute fc_attr;

		Assert(!fc_tle->resjunk);

		/*
		 * 应用与 ExecCheckPlanOutput() 相当的完整性检查。
		 */
		if (fc_targetattnum <= 0 || fc_targetattnum > fc_relDesc->natts)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("table row type and query-specified row type do not match"),
					 errdetail("Query has too many columns.")));
		fc_attr = TupleDescAttr(fc_relDesc, fc_targetattnum - 1);

		if (fc_attr->attisdropped)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("table row type and query-specified row type do not match"),
					 errdetail("Query provides a value for a dropped column at ordinal position %d.",
							   fc_targetattnum)));
		if (exprType((Node *) fc_tle->expr) != fc_attr->atttypid)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("table row type and query-specified row type do not match"),
					 errdetail("Table has type %s at ordinal position %d, but query expects %s.",
							   format_type_be(fc_attr->atttypid),
							   fc_targetattnum,
							   format_type_be(exprType((Node *) fc_tle->expr)))));

		/* 好的，生成代码以执行赋值。 */
		if (fc_evalTargetList)
		{
			/*
			 * 我们必须评估 TLE 的表达式并赋值。我们不
			 * 奇怪地为 “安全” Vars 像
			 * ExecBuildProjectionInfo 那样跳来跳去；这是一条相对少用
			 * 的路径，似乎没有必要为此耗费代码。
			 */
			fc_ExecInitExprRec(fc_tle->expr, fc_state,
							&fc_state->resvalue, &fc_state->resnull);
			/* 这里也不必担心只读性。 */
			fc_scratch.opcode = EEOP_ASSIGN_TMP;
			fc_scratch.d.assign_tmp.resultnum = fc_targetattnum - 1;
			ExprEvalPushStep(fc_state, &fc_scratch);
		}
		else
		{
			/* 只需从外部元组中赋值。 */
			fc_scratch.opcode = EEOP_ASSIGN_OUTER_VAR;
			fc_scratch.d.assign_var.attnum = fc_outerattnum;
			fc_scratch.d.assign_var.resultnum = fc_targetattnum - 1;
			ExprEvalPushStep(fc_state, &fc_scratch);
		}
		fc_outerattnum++;
	}

	/*
	 * 现在生成代码以复制任何未分配的旧列，
	 * 并确保被删除的列设置为 NULL。
	 */
	for (int fc_attnum = 1; fc_attnum <= fc_relDesc->natts; fc_attnum++)
	{
		Form_pg_attribute fc_attr = TupleDescAttr(fc_relDesc, fc_attnum - 1);

		if (fc_attr->attisdropped)
		{
			/* 在 ExprState 的 resvalue/resnull 中放入 null ... */
			fc_scratch.opcode = EEOP_CONST;
			fc_scratch.resvalue = &fc_state->resvalue;
			fc_scratch.resnull = &fc_state->resnull;
			fc_scratch.d.constval.value = (Datum) 0;
			fc_scratch.d.constval.isnull = true;
			ExprEvalPushStep(fc_state, &fc_scratch);
			/* ... 然后将其赋给结果槽 */
			fc_scratch.opcode = EEOP_ASSIGN_TMP;
			fc_scratch.d.assign_tmp.resultnum = fc_attnum - 1;
			ExprEvalPushStep(fc_state, &fc_scratch);
		}
		else if (!bms_is_member(fc_attnum, fc_assignedCols))
		{
			/* 当然是正确的类型，因此不必检查 */
			fc_scratch.opcode = EEOP_ASSIGN_SCAN_VAR;
			fc_scratch.d.assign_var.attnum = fc_attnum - 1;
			fc_scratch.d.assign_var.resultnum = fc_attnum - 1;
			ExprEvalPushStep(fc_state, &fc_scratch);
		}
	}

	fc_scratch.opcode = EEOP_DONE;
	ExprEvalPushStep(fc_state, &fc_scratch);

	fc_ExecReadyExpr(fc_state);

	return fc_projInfo;
}

/*
 * ExecPrepareExpr --- 初始化以在正常的
 * Plan 树上下文之外执行表达式。
 *
 * 这与 ExecInitExpr 的不同之处在于，我们不假设调用者
 * 已在 EState 的每个查询上下文中运行。此外，我们将
 * 传递的表达式树通过 expression_planner() 来准备
 * 执行。 （在普通 Plan 树中，常规规划过程将对
 * 表达式进行适当的转换，但对于独立表达式，这种情况没有发生。）
 */
ExprState * ExecPrepareExpr(Expr *fc_node, EState *fc_estate)
{
	ExprState  *fc_result;
	MemoryContext fc_oldcontext;

	fc_oldcontext = MemoryContextSwitchTo(fc_estate->es_query_cxt);

	fc_node = expression_planner(fc_node);

	fc_result = ExecInitExpr(fc_node, NULL);

	MemoryContextSwitchTo(fc_oldcontext);

	return fc_result;
}

/*
 * ExecPrepareQual --- 初始化以在正常的
 * Plan 树上下文之外执行 qual。
 *
 * 这与 ExecInitQual 的不同之处在于，我们不假设调用者
 * 已在 EState 的每个查询上下文中运行。此外，我们将
 * 传递的表达式树通过 expression_planner() 来准备
 * 执行。 （在普通 Plan 树中，常规规划过程将对
 * 表达式进行适当的转换，但对于独立表达式，这种情况没有发生。）
 */
ExprState * ExecPrepareQual(List *fc_qual, EState *fc_estate)
{
	ExprState  *fc_result;
	MemoryContext fc_oldcontext;

	fc_oldcontext = MemoryContextSwitchTo(fc_estate->es_query_cxt);

	fc_qual = (List *) expression_planner((Expr *) fc_qual);

	fc_result = ExecInitQual(fc_qual, NULL);

	MemoryContextSwitchTo(fc_oldcontext);

	return fc_result;
}

/*
 * ExecPrepareCheck -- 初始化检查约束以在正常的
 * Plan 树上下文之外执行。
 *
 * 请参见 ExecPrepareExpr() 和 ExecInitCheck() 获取详细信息。
 */
ExprState * ExecPrepareCheck(List *fc_qual, EState *fc_estate)
{
	ExprState  *fc_result;
	MemoryContext fc_oldcontext;

	fc_oldcontext = MemoryContextSwitchTo(fc_estate->es_query_cxt);

	fc_qual = (List *) expression_planner((Expr *) fc_qual);

	fc_result = ExecInitCheck(fc_qual, NULL);

	MemoryContextSwitchTo(fc_oldcontext);

	return fc_result;
}

/*
 * 在 Exprs 列表的每个成员上调用 ExecPrepareExpr()，并返回
 * ExprStates 的列表。
 *
 * 请参见 ExecPrepareExpr() 获取详细信息。
 */
List * ExecPrepareExprList(List *fc_nodes, EState *fc_estate)
{
	List	   *fc_result = NIL;
	MemoryContext fc_oldcontext;
	ListCell   *fc_lc;

	/* 确保列表单元节点也处于正确的上下文中 */
	fc_oldcontext = MemoryContextSwitchTo(fc_estate->es_query_cxt);

	foreach(fc_lc, fc_nodes)
	{
		Expr	   *fc_e = (Expr *) lfirst(fc_lc);

		fc_result = lappend(fc_result, ExecPrepareExpr(fc_e, fc_estate));
	}

	MemoryContextSwitchTo(fc_oldcontext);

	return fc_result;
}

/*
 * ExecCheck - 评估检查约束
 *
 * 对于检查约束，null 结果被视为 TRUE，即约束
 * 通过。
 *
 * 检查约束可能已经通过 ExecInitCheck 准备好
 * （可能通过 ExecPrepareCheck），如果调用者以隐式-AND
 * 格式传递它，但使用 ExecInitExpr 或
 * ExecPrepareExpr 准备的常规布尔表达式也可以。
 */
bool ExecCheck(ExprState *fc_state, ExprContext *fc_econtext)
{
	Datum		fc_ret;
	bool		fc_isnull;

	/* 对于空限制列表请短路（在这里和 ExecInitCheck） */
	if (fc_state == NULL)
		return true;

	/* 验证表达式未使用 ExecInitQual 编译 */
	Assert(!(fc_state->flags & EEO_FLAG_IS_QUAL));

	fc_ret = ExecEvalExprSwitchContext(fc_state, fc_econtext, &fc_isnull);

	if (fc_isnull)
		return true;

	return DatumGetBool(fc_ret);
}

/*
 * 准备编译的表达式以供执行。在每个 ExprState 上调用
 * 此方法之前，必须调用此方法。
 *
 * 注意：虽然这当前只调用 ExecReadyInterpretedExpr(),
 * 但这很可能会扩展到进一步的表达式评估方法。
 * 因此，应使用此方法，而不是直接调用
 * ExecReadyInterpretedExpr()。
 */
static void fc_ExecReadyExpr(ExprState *fc_state)
{
	if (jit_compile_expr(fc_state))
		return;

	ExecReadyInterpretedExpr(fc_state);
}

/*
 * 将评估节点所需的步骤附加到 ExprState->steps 中，
 * 可能递归进入节点的子表达式。
 *
 * node - 要评估的表达式
 * state - 要将必要操作附加到其 ->steps 的 ExprState
 * resv / resnull - 将节点的结果存储到哪里
 */
static void fc_ExecInitExprRec(Expr *fc_node, ExprState *fc_state,
				Datum *fc_resv, bool *fc_resnull)
{
	ExprEvalStep fc_scratch = {0};

	/* 防止因过于复杂的表达式导致栈溢出 */
	check_stack_depth();

	/* 步骤的输出位置始终是调用者给我们的 */
	Assert(fc_resv != NULL && fc_resnull != NULL);
	fc_scratch.resvalue = fc_resv;
	fc_scratch.resnull = fc_resnull;

	/* 案例应按它们在 enum NodeTag 中的顺序排列 */
	switch (nodeTag(fc_node))
	{
		case T_Var:
			{
				Var		   *fc_variable = (Var *) fc_node;

				if (fc_variable->varattno == InvalidAttrNumber)
				{
					/* 整行 Var */
					fc_ExecInitWholeRowVar(&fc_scratch, fc_variable, fc_state);
				}
				else if (fc_variable->varattno <= 0)
				{
					/* 系统列 */
					fc_scratch.d.var.attnum = fc_variable->varattno;
					fc_scratch.d.var.vartype = fc_variable->vartype;
					switch (fc_variable->varno)
					{
						case INNER_VAR:
							fc_scratch.opcode = EEOP_INNER_SYSVAR;
							break;
						case OUTER_VAR:
							fc_scratch.opcode = EEOP_OUTER_SYSVAR;
							break;

							/* INDEX_VAR 由默认情况处理 */

						default:
							fc_scratch.opcode = EEOP_SCAN_SYSVAR;
							break;
					}
				}
				else
				{
					/* 常规用户列 */
					fc_scratch.d.var.attnum = fc_variable->varattno - 1;
					fc_scratch.d.var.vartype = fc_variable->vartype;
					switch (fc_variable->varno)
					{
						case INNER_VAR:
							fc_scratch.opcode = EEOP_INNER_VAR;
							break;
						case OUTER_VAR:
							fc_scratch.opcode = EEOP_OUTER_VAR;
							break;

							/* INDEX_VAR 由默认情况处理 */

						default:
							fc_scratch.opcode = EEOP_SCAN_VAR;
							break;
					}
				}

				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_Const:
			{
				Const	   *fc_con = (Const *) fc_node;

				fc_scratch.opcode = EEOP_CONST;
				fc_scratch.d.constval.value = fc_con->constvalue;
				fc_scratch.d.constval.isnull = fc_con->constisnull;

				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_Param:
			{
				Param	   *fc_param = (Param *) fc_node;
				ParamListInfo fc_params;

				switch (fc_param->paramkind)
				{
					case PARAM_EXEC:
						fc_scratch.opcode = EEOP_PARAM_EXEC;
						fc_scratch.d.param.paramid = fc_param->paramid;
						fc_scratch.d.param.paramtype = fc_param->paramtype;
						ExprEvalPushStep(fc_state, &fc_scratch);
						break;
					case PARAM_EXTERN:

						/*
						 * 如果我们有相关的 ParamCompileHook，则使用它；
						 * 否则编译标准的 EEOP_PARAM_EXTERN
						 * 步骤。如果提供了 ext_params，则优先于
						 * 父节点的 EState 中的信息（如果有）。
						 */
						if (fc_state->ext_params)
							fc_params = fc_state->ext_params;
						else if (fc_state->parent &&
								 fc_state->parent->state)
							fc_params = fc_state->parent->state->es_param_list_info;
						else
							fc_params = NULL;
						if (fc_params && fc_params->paramCompile)
						{
							fc_params->paramCompile(fc_params, fc_param, fc_state,
												 fc_resv, fc_resnull);
						}
						else
						{
							fc_scratch.opcode = EEOP_PARAM_EXTERN;
							fc_scratch.d.param.paramid = fc_param->paramid;
							fc_scratch.d.param.paramtype = fc_param->paramtype;
							ExprEvalPushStep(fc_state, &fc_scratch);
						}
						break;
					default:
						elog(ERROR, "unrecognized paramkind: %d",
							 (int) fc_param->paramkind);
						break;
				}
				break;
			}

		case T_Aggref:
			{
				Aggref	   *fc_aggref = (Aggref *) fc_node;

				fc_scratch.opcode = EEOP_AGGREF;
				fc_scratch.d.aggref.aggno = fc_aggref->aggno;

				if (fc_state->parent && IsA(fc_state->parent, AggState))
				{
					AggState   *fc_aggstate = (AggState *) fc_state->parent;

					fc_aggstate->aggs = lappend(fc_aggstate->aggs, fc_aggref);
				}
				else
				{
					/* 规划器搞砸了 */
					elog(ERROR, "Aggref found in non-Agg plan node");
				}

				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_GroupingFunc:
			{
				GroupingFunc *fc_grp_node = (GroupingFunc *) fc_node;
				Agg		   *fc_agg;

				if (!fc_state->parent || !IsA(fc_state->parent, AggState) ||
					!IsA(fc_state->parent->plan, Agg))
					elog(ERROR, "GroupingFunc found in non-Agg plan node");

				fc_scratch.opcode = EEOP_GROUPING_FUNC;

				fc_agg = (Agg *) (fc_state->parent->plan);

				if (fc_agg->groupingSets)
					fc_scratch.d.grouping_func.clauses = fc_grp_node->cols;
				else
					fc_scratch.d.grouping_func.clauses = NIL;

				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_WindowFunc:
			{
				WindowFunc *fc_wfunc = (WindowFunc *) fc_node;
				WindowFuncExprState *fc_wfstate = makeNode(WindowFuncExprState);

				fc_wfstate->wfunc = fc_wfunc;

				if (fc_state->parent && IsA(fc_state->parent, WindowAggState))
				{
					WindowAggState *fc_winstate = (WindowAggState *) fc_state->parent;
					int			fc_nfuncs;

					fc_winstate->funcs = lappend(fc_winstate->funcs, fc_wfstate);
					fc_nfuncs = ++fc_winstate->numfuncs;
					if (fc_wfunc->winagg)
						fc_winstate->numaggs++;

					/* 现在使用旧风格的表达式初始化 agg */
					fc_wfstate->args = ExecInitExprList(fc_wfunc->args,
													 fc_state->parent);
					fc_wfstate->aggfilter = ExecInitExpr(fc_wfunc->aggfilter,
													  fc_state->parent);

					/*
					 * 如果windowfunc的参数包含任何
					 * windowfunc，则进行投诉；嵌套的window函数在语义上是
					 * 没有意义的。 （这应该在早期就被捕获，
					 * 但我们仍在这里防范它。）
					 */
					if (fc_nfuncs != fc_winstate->numfuncs)
						ereport(ERROR,
								(errcode(ERRCODE_WINDOWING_ERROR),
								 errmsg("window function calls cannot be nested")));
				}
				else
				{
					/* 规划器搞砸了 */
					elog(ERROR, "WindowFunc found in non-WindowAgg plan node");
				}

				fc_scratch.opcode = EEOP_WINDOW_FUNC;
				fc_scratch.d.window_func.wfstate = fc_wfstate;
				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_SubscriptingRef:
			{
				SubscriptingRef *fc_sbsref = (SubscriptingRef *) fc_node;

				fc_ExecInitSubscriptingRef(&fc_scratch, fc_sbsref, fc_state, fc_resv, fc_resnull);
				break;
			}

		case T_FuncExpr:
			{
				FuncExpr   *fc_func = (FuncExpr *) fc_node;

				fc_ExecInitFunc(&fc_scratch, fc_node,
							 fc_func->args, fc_func->funcid, fc_func->inputcollid,
							 fc_state);
				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_OpExpr:
			{
				OpExpr	   *fc_op = (OpExpr *) fc_node;

				fc_ExecInitFunc(&fc_scratch, fc_node,
							 fc_op->args, fc_op->opfuncid, fc_op->inputcollid,
							 fc_state);
				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_DistinctExpr:
			{
				DistinctExpr *fc_op = (DistinctExpr *) fc_node;

				fc_ExecInitFunc(&fc_scratch, fc_node,
							 fc_op->args, fc_op->opfuncid, fc_op->inputcollid,
							 fc_state);

				/*
				 * 将调用指令的操作码更改为EEOP_DISTINCT。
				 *
				 * XXX：历史上我们没有调用函数使用
				 * pgstat基础设施 - 这似乎与
				 * 我们为正常函数*和*操作符评估所做的事情不一致。 如果
				 * 我们决定在这里这样做，我们可能需要单独的
				 * 对于FUSAGE的操作码。
				 */
				fc_scratch.opcode = EEOP_DISTINCT;
				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_NullIfExpr:
			{
				NullIfExpr *fc_op = (NullIfExpr *) fc_node;

				fc_ExecInitFunc(&fc_scratch, fc_node,
							 fc_op->args, fc_op->opfuncid, fc_op->inputcollid,
							 fc_state);

				/*
				 * 如果第一个参数是varlena类型，我们需要确保
				 * 传递给比较函数的值是一个
				 * 只读指针。
				 */
				fc_scratch.d.func.make_ro =
					(get_typlen(exprType((Node *) linitial(fc_op->args))) == -1);

				/*
				 * 将调用指令的操作码更改为EEOP_NULLIF。
				 *
				 * XXX：历史上我们没有调用函数使用
				 * pgstat基础设施 - 这似乎与
				 * 我们为正常函数*和*操作符评估所做的事情不一致。 如果
				 * 我们决定在这里这样做，我们可能需要单独的
				 * 对于FUSAGE的操作码。
				 */
				fc_scratch.opcode = EEOP_NULLIF;
				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_ScalarArrayOpExpr:
			{
				ScalarArrayOpExpr *fc_opexpr = (ScalarArrayOpExpr *) fc_node;
				Expr	   *fc_scalararg;
				Expr	   *fc_arrayarg;
				FmgrInfo   *fc_finfo;
				FunctionCallInfo fcinfo;
				AclResult	fc_aclresult;
				Oid			fc_cmpfuncid;

				/*
				 * 选择正确的比较函数。 当我们执行散列
				 * NOT IN子句时，opfuncid将是
				 * 不等比较函数，negfuncid将设置为等于。
				 * 我们需要使用等于函数来进行哈希探查。
				 */
				if (OidIsValid(fc_opexpr->negfuncid))
				{
					Assert(OidIsValid(fc_opexpr->hashfuncid));
					fc_cmpfuncid = fc_opexpr->negfuncid;
				}
				else
					fc_cmpfuncid = fc_opexpr->opfuncid;

				Assert(list_length(fc_opexpr->args) == 2);
				fc_scalararg = (Expr *) linitial(fc_opexpr->args);
				fc_arrayarg = (Expr *) lsecond(fc_opexpr->args);

				/* 检查调用函数的权限 */
				fc_aclresult = pg_proc_aclcheck(fc_cmpfuncid,
											 GetUserId(),
											 ACL_EXECUTE);
				if (fc_aclresult != ACLCHECK_OK)
					aclcheck_error(fc_aclresult, OBJECT_FUNCTION,
								   get_func_name(fc_cmpfuncid));
				InvokeFunctionExecuteHook(fc_cmpfuncid);

				if (OidIsValid(fc_opexpr->hashfuncid))
				{
					fc_aclresult = pg_proc_aclcheck(fc_opexpr->hashfuncid,
												 GetUserId(),
												 ACL_EXECUTE);
					if (fc_aclresult != ACLCHECK_OK)
						aclcheck_error(fc_aclresult, OBJECT_FUNCTION,
									   get_func_name(fc_opexpr->hashfuncid));
					InvokeFunctionExecuteHook(fc_opexpr->hashfuncid);
				}

				/* 设置主fmgr查找信息 */
				fc_finfo = palloc0(sizeof(FmgrInfo));
				fcinfo = palloc0(SizeForFunctionCallInfo(2));
				fmgr_info(fc_cmpfuncid, fc_finfo);
				fmgr_info_set_expr((Node *) fc_node, fc_finfo);
				InitFunctionCallInfoData(*fcinfo, fc_finfo, 2,
										 fc_opexpr->inputcollid, NULL, NULL);

				/*
				 * 如果hashfuncid被设置，我们将创建一个EEOP_HASHED_SCALARARRAYOP
				 * 步骤，而不是EEOP_SCALARARRAYOP。这提供了比正常线性搜索要快得多的
				 * 查找性能，当数组中的项目数量不是非常
				 * 小。
				 */
				if (OidIsValid(fc_opexpr->hashfuncid))
				{
					/* 直接将标量评估到左侧函数参数中 */
					fc_ExecInitExprRec(fc_scalararg, fc_state,
									&fcinfo->args[0].value, &fcinfo->args[0].isnull);

					/*
					 * 将数组参数评估到我们的返回值中。 这没有
					 * 危险，因为返回值确保被
					 * EEOP_HASHED_SCALARARRAYOP覆盖，并且不会传递到
					 * 任何其他表达式。
					 */
					fc_ExecInitExprRec(fc_arrayarg, fc_state, fc_resv, fc_resnull);

					/* 并执行操作 */
					fc_scratch.opcode = EEOP_HASHED_SCALARARRAYOP;
					fc_scratch.d.hashedscalararrayop.inclause = fc_opexpr->useOr;
					fc_scratch.d.hashedscalararrayop.finfo = fc_finfo;
					fc_scratch.d.hashedscalararrayop.fcinfo_data = fcinfo;
					fc_scratch.d.hashedscalararrayop.saop = fc_opexpr;


					ExprEvalPushStep(fc_state, &fc_scratch);
				}
				else
				{
					/* 直接将标量评估到左侧函数参数中 */
					fc_ExecInitExprRec(fc_scalararg, fc_state,
									&fcinfo->args[0].value,
									&fcinfo->args[0].isnull);

					/*
					 * 将数组参数评估到我们的返回值中。 这没有
					 * 危险，因为返回值确保被
					 * EEOP_SCALARARRAYOP覆盖，并且
					 * 不会传递到任何其他表达式。
					 */
					fc_ExecInitExprRec(fc_arrayarg, fc_state, fc_resv, fc_resnull);

					/* 并执行操作 */
					fc_scratch.opcode = EEOP_SCALARARRAYOP;
					fc_scratch.d.scalararrayop.element_type = InvalidOid;
					fc_scratch.d.scalararrayop.useOr = fc_opexpr->useOr;
					fc_scratch.d.scalararrayop.finfo = fc_finfo;
					fc_scratch.d.scalararrayop.fcinfo_data = fcinfo;
					fc_scratch.d.scalararrayop.fn_addr = fc_finfo->fn_addr;
					ExprEvalPushStep(fc_state, &fc_scratch);
				}
				break;
			}

		case T_BoolExpr:
			{
				BoolExpr   *fc_boolexpr = (BoolExpr *) fc_node;
				int			fc_nargs = list_length(fc_boolexpr->args);
				List	   *fc_adjust_jumps = NIL;
				int			fc_off;
				ListCell   *fc_lc;

				/* 分配所有AND/OR步骤使用的临时内存 */
				if (fc_boolexpr->boolop != NOT_EXPR)
					fc_scratch.d.boolexpr.anynull = (bool *) palloc(sizeof(bool));

				/*
				 * 对于每个参数评估参数本身，然后
				 * 执行布尔操作的适当处理。
				 *
				 * 我们可以将每个参数评估到我们的结果区域，因为
				 * 短路逻辑意味着我们只需记住
				 * 先前的NULL值。
				 *
				 * AND/OR被分成单独的STEP_FIRST（一个）/STEP（零
				 * 或多个）/STEP_LAST（一个）步骤，因为每个步骤
				 * 必须执行不同的工作。 第一个/最后一个分割是有效的
				 * 因为AND/OR至少有两个参数。
				 */
				fc_off = 0;
				foreach(fc_lc, fc_boolexpr->args)
				{
					Expr	   *fc_arg = (Expr *) lfirst(fc_lc);

					/* 将参数评估到我们的输出变量中 */
					fc_ExecInitExprRec(fc_arg, fc_state, fc_resv, fc_resnull);

					/* 执行适当的步骤类型 */
					switch (fc_boolexpr->boolop)
					{
						case AND_EXPR:
							Assert(fc_nargs >= 2);

							if (fc_off == 0)
								fc_scratch.opcode = EEOP_BOOL_AND_STEP_FIRST;
							else if (fc_off + 1 == fc_nargs)
								fc_scratch.opcode = EEOP_BOOL_AND_STEP_LAST;
							else
								fc_scratch.opcode = EEOP_BOOL_AND_STEP;
							break;
						case OR_EXPR:
							Assert(fc_nargs >= 2);

							if (fc_off == 0)
								fc_scratch.opcode = EEOP_BOOL_OR_STEP_FIRST;
							else if (fc_off + 1 == fc_nargs)
								fc_scratch.opcode = EEOP_BOOL_OR_STEP_LAST;
							else
								fc_scratch.opcode = EEOP_BOOL_OR_STEP;
							break;
						case NOT_EXPR:
							Assert(fc_nargs == 1);

							fc_scratch.opcode = EEOP_BOOL_NOT_STEP;
							break;
						default:
							elog(ERROR, "unrecognized boolop: %d",
								 (int) fc_boolexpr->boolop);
							break;
					}

					fc_scratch.d.boolexpr.jumpdone = -1;
					ExprEvalPushStep(fc_state, &fc_scratch);
					fc_adjust_jumps = lappend_int(fc_adjust_jumps,
											   fc_state->steps_len - 1);
					fc_off++;
				}

				/* 调整跳转目标 */
				foreach(fc_lc, fc_adjust_jumps)
				{
					ExprEvalStep *fc_as = &fc_state->steps[lfirst_int(fc_lc)];

					Assert(fc_as->d.boolexpr.jumpdone == -1);
					fc_as->d.boolexpr.jumpdone = fc_state->steps_len;
				}

				break;
			}

		case T_SubPlan:
			{
				SubPlan    *fc_subplan = (SubPlan *) fc_node;
				SubPlanState *fc_sstate;

				/*
				 * MULTIEXPR子计划的实际执行已经完成。
				 * 我们在这里要做的是返回一个虚拟的NULL记录
				 * 值，以防这个目标列表元素被分配
				 * 到某个地方。
				 */
				if (fc_subplan->subLinkType == MULTIEXPR_SUBLINK)
				{
					fc_scratch.opcode = EEOP_CONST;
					fc_scratch.d.constval.value = (Datum) 0;
					fc_scratch.d.constval.isnull = true;
					ExprEvalPushStep(fc_state, &fc_scratch);
					break;
				}

				if (!fc_state->parent)
					elog(ERROR, "SubPlan found with no parent plan");

				fc_sstate = ExecInitSubPlan(fc_subplan, fc_state->parent);

				/* 将SubPlanState节点添加到state->parent->subPlan */
				fc_state->parent->subPlan = lappend(fc_state->parent->subPlan,
												 fc_sstate);

				fc_scratch.opcode = EEOP_SUBPLAN;
				fc_scratch.d.subplan.sstate = fc_sstate;

				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_FieldSelect:
			{
				FieldSelect *fc_fselect = (FieldSelect *) fc_node;

				/* 将行/记录参数评估到结果区域 */
				fc_ExecInitExprRec(fc_fselect->arg, fc_state, fc_resv, fc_resnull);

				/* 并提取字段 */
				fc_scratch.opcode = EEOP_FIELDSELECT;
				fc_scratch.d.fieldselect.fieldnum = fc_fselect->fieldnum;
				fc_scratch.d.fieldselect.resulttype = fc_fselect->resulttype;
				fc_scratch.d.fieldselect.rowcache.cacheptr = NULL;

				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_FieldStore:
			{
				FieldStore *fc_fstore = (FieldStore *) fc_node;
				TupleDesc	fc_tupDesc;
				ExprEvalRowtypeCache *fc_rowcachep;
				Datum	   *fc_values;
				bool	   *fc_nulls;
				int			fc_ncolumns;
				ListCell   *fc_l1,
						   *fc_l2;

				/* 找出复合类型中的列数 */
				fc_tupDesc = lookup_rowtype_tupdesc(fc_fstore->resulttype, -1);
				fc_ncolumns = fc_tupDesc->natts;
				ReleaseTupleDesc(fc_tupDesc);

				/* 为列值创建工作区 */
				fc_values = (Datum *) palloc(sizeof(Datum) * fc_ncolumns);
				fc_nulls = (bool *) palloc(sizeof(bool) * fc_ncolumns);

				/* 创建共享复合类型查找缓存结构 */
				fc_rowcachep = palloc(sizeof(ExprEvalRowtypeCache));
				fc_rowcachep->cacheptr = NULL;

				/* 发出代码以评估复合输入值 */
				fc_ExecInitExprRec(fc_fstore->arg, fc_state, fc_resv, fc_resnull);

				/* 接下来，将输入元组变形为我们的工作空间 */
				fc_scratch.opcode = EEOP_FIELDSTORE_DEFORM;
				fc_scratch.d.fieldstore.fstore = fc_fstore;
				fc_scratch.d.fieldstore.rowcache = fc_rowcachep;
				fc_scratch.d.fieldstore.values = fc_values;
				fc_scratch.d.fieldstore.nulls = fc_nulls;
				fc_scratch.d.fieldstore.ncolumns = fc_ncolumns;
				ExprEvalPushStep(fc_state, &fc_scratch);

				/* 评估新字段值，存储在工作空间列中 */
				forboth(fc_l1, fc_fstore->newvals, fc_l2, fc_fstore->fieldnums)
				{
					Expr	   *fc_e = (Expr *) lfirst(fc_l1);
					AttrNumber	fc_fieldnum = lfirst_int(fc_l2);
					Datum	   *fc_save_innermost_caseval;
					bool	   *fc_save_innermost_casenull;

					if (fc_fieldnum <= 0 || fc_fieldnum > fc_ncolumns)
						elog(ERROR, "field number %d is out of range in FieldStore",
							 fc_fieldnum);

					
/*
					 * 使用 CaseTestExpr 机制将正在替换的字段的旧值传递下来；这是必要的，因为新的值本身可能是一个 FieldStore 或
					 * SubscriptingRef，需要获取并修改旧值。重用 CASE 机制是安全的，因为在这里到需要值的地方之间不可能有 CASE，
					 * 而字段赋值也不能在 CASE 中。因此，保存和恢复 innermost_caseval 只是过度小心，但我们还是这样做。
					 *
					 * 另一个不明显的观点是，可以安全地将字段的 values[]/nulls[] 条目同时用作此子表达式的 caseval 源和结果地址。
					 * 这是可以的，只有因为 (1) FieldStore 和 SubscriptingRef 首先评估它们的 arg 或 refexpr 输入，(2) 任何这样的 CaseTestExpr 直接是
					 * arg 或 refexpr 输入。因此，caseval 的任何读取都会在有机会覆盖它之前发生。此外，如果 newvals/fieldnums 列表中的多个条目
					 * 针对同一字段，它们实际上将从左到右应用，这正是我们想要的。
					 */
					fc_save_innermost_caseval = fc_state->innermost_caseval;
					fc_save_innermost_casenull = fc_state->innermost_casenull;
					fc_state->innermost_caseval = &fc_values[fc_fieldnum - 1];
					fc_state->innermost_casenull = &fc_nulls[fc_fieldnum - 1];

					fc_ExecInitExprRec(fc_e, fc_state,
									&fc_values[fc_fieldnum - 1],
									&fc_nulls[fc_fieldnum - 1]);

					fc_state->innermost_caseval = fc_save_innermost_caseval;
					fc_state->innermost_casenull = fc_save_innermost_casenull;
				}

				/* 最后，形成结果元组 */
				fc_scratch.opcode = EEOP_FIELDSTORE_FORM;
				fc_scratch.d.fieldstore.fstore = fc_fstore;
				fc_scratch.d.fieldstore.rowcache = fc_rowcachep;
				fc_scratch.d.fieldstore.values = fc_values;
				fc_scratch.d.fieldstore.nulls = fc_nulls;
				fc_scratch.d.fieldstore.ncolumns = fc_ncolumns;
				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_RelabelType:
			{
				/* relabel 在运行时不需要做任何事情 */
				RelabelType *fc_relabel = (RelabelType *) fc_node;

				fc_ExecInitExprRec(fc_relabel->arg, fc_state, fc_resv, fc_resnull);
				break;
			}

		case T_CoerceViaIO:
			{
				CoerceViaIO *fc_iocoerce = (CoerceViaIO *) fc_node;
				Oid			fc_iofunc;
				bool		fc_typisvarlena;
				Oid			fc_typioparam;
				FunctionCallInfo fc_fcinfo_in;

				/* 将参数评估到步骤的结果区域 */
				fc_ExecInitExprRec(fc_iocoerce->arg, fc_state, fc_resv, fc_resnull);

				/*
				 * 准备输出和输入函数调用，以便在单个评估步骤中评估以提高速度 - 这是一个非常常见的操作。
				 *
				 * 在这里我们不检查权限，因为类型的输入/输出函数假定可以被所有人执行。
				 */
				fc_scratch.opcode = EEOP_IOCOERCE;

				/* 查找源类型的输出函数 */
				fc_scratch.d.iocoerce.finfo_out = palloc0(sizeof(FmgrInfo));
				fc_scratch.d.iocoerce.fcinfo_data_out = palloc0(SizeForFunctionCallInfo(1));

				getTypeOutputInfo(exprType((Node *) fc_iocoerce->arg),
								  &fc_iofunc, &fc_typisvarlena);
				fmgr_info(fc_iofunc, fc_scratch.d.iocoerce.finfo_out);
				fmgr_info_set_expr((Node *) fc_node, fc_scratch.d.iocoerce.finfo_out);
				InitFunctionCallInfoData(*fc_scratch.d.iocoerce.fcinfo_data_out,
										 fc_scratch.d.iocoerce.finfo_out,
										 1, InvalidOid, NULL, NULL);

				/* 查找结果类型的输入函数 */
				fc_scratch.d.iocoerce.finfo_in = palloc0(sizeof(FmgrInfo));
				fc_scratch.d.iocoerce.fcinfo_data_in = palloc0(SizeForFunctionCallInfo(3));

				getTypeInputInfo(fc_iocoerce->resulttype,
								 &fc_iofunc, &fc_typioparam);
				fmgr_info(fc_iofunc, fc_scratch.d.iocoerce.finfo_in);
				fmgr_info_set_expr((Node *) fc_node, fc_scratch.d.iocoerce.finfo_in);
				InitFunctionCallInfoData(*fc_scratch.d.iocoerce.fcinfo_data_in,
										 fc_scratch.d.iocoerce.finfo_in,
										 3, InvalidOid, NULL, NULL);

				/*
				 * 我们可以预加载输入函数的第二和第三个参数，因为它们是常量。
				 */
				fc_fcinfo_in = fc_scratch.d.iocoerce.fcinfo_data_in;
				fc_fcinfo_in->args[1].value = ObjectIdGetDatum(fc_typioparam);
				fc_fcinfo_in->args[1].isnull = false;
				fc_fcinfo_in->args[2].value = Int32GetDatum(-1);
				fc_fcinfo_in->args[2].isnull = false;

				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_ArrayCoerceExpr:
			{
				ArrayCoerceExpr *fc_acoerce = (ArrayCoerceExpr *) fc_node;
				Oid			fc_resultelemtype;
				ExprState  *fc_elemstate;

				/* 将参数评估到步骤的结果区域 */
				fc_ExecInitExprRec(fc_acoerce->arg, fc_state, fc_resv, fc_resnull);

				fc_resultelemtype = get_element_type(fc_acoerce->resulttype);
				if (!OidIsValid(fc_resultelemtype))
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
							 errmsg("target type is not an array")));

				/*
				 * 为每个元素的表达式构造子表达式；
				 * 但在检查其是否平凡之前不要准备它。
				 * 我们假设它没有 Var 引用，但有一个
				 * CaseTestExpr 表示源数组元素值。
				 */
				fc_elemstate = makeNode(ExprState);
				fc_elemstate->expr = fc_acoerce->elemexpr;
				fc_elemstate->parent = fc_state->parent;
				fc_elemstate->ext_params = fc_state->ext_params;

				fc_elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
				fc_elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));

				fc_ExecInitExprRec(fc_acoerce->elemexpr, fc_elemstate,
								&fc_elemstate->resvalue, &fc_elemstate->resnull);

				if (fc_elemstate->steps_len == 1 &&
					fc_elemstate->steps[0].opcode == EEOP_CASE_TESTVAL)
				{
					/* 平凡，因此我们在运行时不需要每个元素的工作 */
					fc_elemstate = NULL;
				}
				else
				{
					/* 不平凡，因此附加一个完成步骤 */
					fc_scratch.opcode = EEOP_DONE;
					ExprEvalPushStep(fc_elemstate, &fc_scratch);
					/* 并准备子表达式 */
					fc_ExecReadyExpr(fc_elemstate);
				}

				fc_scratch.opcode = EEOP_ARRAYCOERCE;
				fc_scratch.d.arraycoerce.elemexprstate = fc_elemstate;
				fc_scratch.d.arraycoerce.resultelemtype = fc_resultelemtype;

				if (fc_elemstate)
				{
					/* 为 array_map 设置工作空间 */
					fc_scratch.d.arraycoerce.amstate =
						(ArrayMapState *) palloc0(sizeof(ArrayMapState));
				}
				else
				{
					/* 如果没有子表达式，则无需工作空间 */
					fc_scratch.d.arraycoerce.amstate = NULL;
				}

				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_ConvertRowtypeExpr:
			{
				ConvertRowtypeExpr *fc_convert = (ConvertRowtypeExpr *) fc_node;
				ExprEvalRowtypeCache *fc_rowcachep;

				/* 缓存结构必须是脱线的以节省空间 */
				fc_rowcachep = palloc(2 * sizeof(ExprEvalRowtypeCache));
				fc_rowcachep[0].cacheptr = NULL;
				fc_rowcachep[1].cacheptr = NULL;

				/* 将参数评估到步骤的结果区域 */
				fc_ExecInitExprRec(fc_convert->arg, fc_state, fc_resv, fc_resnull);

				/* 并推送转换步骤 */
				fc_scratch.opcode = EEOP_CONVERT_ROWTYPE;
				fc_scratch.d.convert_rowtype.inputtype =
					exprType((Node *) fc_convert->arg);
				fc_scratch.d.convert_rowtype.outputtype = fc_convert->resulttype;
				fc_scratch.d.convert_rowtype.incache = &fc_rowcachep[0];
				fc_scratch.d.convert_rowtype.outcache = &fc_rowcachep[1];
				fc_scratch.d.convert_rowtype.map = NULL;

				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

			/* 注意 CaseWhen 表达式是在此块内处理的 */
		case T_CaseExpr:
			{
				CaseExpr   *fc_caseExpr = (CaseExpr *) fc_node;
				List	   *fc_adjust_jumps = NIL;
				Datum	   *fc_caseval = NULL;
				bool	   *fc_casenull = NULL;
				ListCell   *fc_lc;

				/*
				 * 如果有测试表达式，我们必须评估它并
				 * 保存该值，以便 CaseTestExpr 占位符可以找到
				 * 它。
				 */
				if (fc_caseExpr->arg != NULL)
				{
					/* 将测试表达式评估到 caseval/casenull 工作空间 */
					fc_caseval = palloc(sizeof(Datum));
					fc_casenull = palloc(sizeof(bool));

					fc_ExecInitExprRec(fc_caseExpr->arg, fc_state,
									fc_caseval, fc_casenull);

					/*
					 * 由于值可能被多次读取，强制为只读
					 * - 但只有在它可能是扩展数据的情况下。
					 */
					if (get_typlen(exprType((Node *) fc_caseExpr->arg)) == -1)
					{
						/* 就地改变 caseval */
						fc_scratch.opcode = EEOP_MAKE_READONLY;
						fc_scratch.resvalue = fc_caseval;
						fc_scratch.resnull = fc_casenull;
						fc_scratch.d.make_readonly.value = fc_caseval;
						fc_scratch.d.make_readonly.isnull = fc_casenull;
						ExprEvalPushStep(fc_state, &fc_scratch);
						/* 恢复临时字段的正常设置 */
						fc_scratch.resvalue = fc_resv;
						fc_scratch.resnull = fc_resnull;
					}
				}

				/*
				 * 准备依次评估每个 WHEN 子句；一旦其中一个为真，我们返回
				 * 相应 THEN 子句的值。如果没有为真，我们返回
				 * ELSE 子句的值，或者如果没有，则返回 NULL。
				 */
				foreach(fc_lc, fc_caseExpr->args)
				{
					CaseWhen   *fc_when = (CaseWhen *) lfirst(fc_lc);
					Datum	   *fc_save_innermost_caseval;
					bool	   *fc_save_innermost_casenull;
					int			fc_whenstep;

					/*
					 * 使 testexpr 结果对条件内的 CaseTestExpr 节点可用。
					 * 我们必须保存和恢复最内层 caseval 字段的先前设置，
					 * 以防这个节点本身在一个更大的 CASE 内。
					 *
					 * 如果没有测试表达式，我们实际上不需要
					 * 保存和恢复这些字段；但无条件地这样做代码更少。
					 */
					fc_save_innermost_caseval = fc_state->innermost_caseval;
					fc_save_innermost_casenull = fc_state->innermost_casenull;
					fc_state->innermost_caseval = fc_caseval;
					fc_state->innermost_casenull = fc_casenull;

					/* 将条件评估到 CASE 的结果变量中 */
					fc_ExecInitExprRec(fc_when->expr, fc_state, fc_resv, fc_resnull);

					fc_state->innermost_caseval = fc_save_innermost_caseval;
					fc_state->innermost_casenull = fc_save_innermost_casenull;

					/* 如果 WHEN 结果不为真，跳转到下一个 CASE 分支 */
					fc_scratch.opcode = EEOP_JUMP_IF_NOT_TRUE;
					fc_scratch.d.jump.jumpdone = -1;	/* 稍后计算 */
					ExprEvalPushStep(fc_state, &fc_scratch);
					fc_whenstep = fc_state->steps_len - 1;

					/*
					 * 如果 WHEN 结果为真，评估 THEN 结果，将其存储
					 * 到 CASE 的结果变量中。
					 */
					fc_ExecInitExprRec(fc_when->result, fc_state, fc_resv, fc_resnull);

					/* 发出跳转步骤以跳到 CASE 代码的末尾 */
					fc_scratch.opcode = EEOP_JUMP;
					fc_scratch.d.jump.jumpdone = -1;	/* 稍后计算 */
					ExprEvalPushStep(fc_state, &fc_scratch);

					/*
					 * 还不知道跳转的地址，一旦整个 CASE 表达式构建完成后计算。
					 */
					fc_adjust_jumps = lappend_int(fc_adjust_jumps,
											   fc_state->steps_len - 1);

					/*
					 * 但我们现在可以设置 WHEN 测试的跳转目标，使其
					 * 跳转到下一个 WHEN 子表达式或 ELSE。
					 */
					fc_state->steps[fc_whenstep].d.jump.jumpdone = fc_state->steps_len;
				}

				/* transformCaseExpr 总是添加一个默认值 */
				Assert(fc_caseExpr->defresult);

				/* 将 ELSE 表达式评估到 CASE 的结果变量中 */
				fc_ExecInitExprRec(fc_caseExpr->defresult, fc_state,
								fc_resv, fc_resnull);

				/* 调整跳转目标 */
				foreach(fc_lc, fc_adjust_jumps)
				{
					ExprEvalStep *fc_as = &fc_state->steps[lfirst_int(fc_lc)];

					Assert(fc_as->opcode == EEOP_JUMP);
					Assert(fc_as->d.jump.jumpdone == -1);
					fc_as->d.jump.jumpdone = fc_state->steps_len;
				}

				break;
			}

		case T_CaseTestExpr:
			{
				/*
				 * 从最内层 caseval 确定的位置读取。注意
				 * 如果该节点实际上不在 CaseExpr、ArrayCoerceExpr 等结构内，
				 * innermost_caseval 可能为 NULL。
				 * 这是因为系统的某些部分滥用
				 * CaseTestExpr 导致从外部提供的值读取
				 * 在 econtext->caseValue_datum 中。我们将在运行时处理这种
				 * 情况。
				 */
				fc_scratch.opcode = EEOP_CASE_TESTVAL;
				fc_scratch.d.casetest.value = fc_state->innermost_caseval;
				fc_scratch.d.casetest.isnull = fc_state->innermost_casenull;

				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_ArrayExpr:
			{
				ArrayExpr  *fc_arrayexpr = (ArrayExpr *) fc_node;
				int			fc_nelems = list_length(fc_arrayexpr->elements);
				ListCell   *fc_lc;
				int			fc_elemoff;

				/*
				 * 通过计算每个元素来评估，然后形成数组。元素被计算到与 ARRAYEXPR 步骤相关的临时数组中。
				 */
				fc_scratch.opcode = EEOP_ARRAYEXPR;
				fc_scratch.d.arrayexpr.elemvalues =
					(Datum *) palloc(sizeof(Datum) * fc_nelems);
				fc_scratch.d.arrayexpr.elemnulls =
					(bool *) palloc(sizeof(bool) * fc_nelems);
				fc_scratch.d.arrayexpr.nelems = fc_nelems;

				/* 填充步骤的其余字段 */
				fc_scratch.d.arrayexpr.multidims = fc_arrayexpr->multidims;
				fc_scratch.d.arrayexpr.elemtype = fc_arrayexpr->element_typeid;

				/* 进行一次性目录查找以获取类型信息 */
				get_typlenbyvalalign(fc_arrayexpr->element_typeid,
									 &fc_scratch.d.arrayexpr.elemlength,
									 &fc_scratch.d.arrayexpr.elembyval,
									 &fc_scratch.d.arrayexpr.elemalign);

				/* 准备评估所有参数 */
				fc_elemoff = 0;
				foreach(fc_lc, fc_arrayexpr->elements)
				{
					Expr	   *fc_e = (Expr *) lfirst(fc_lc);

					fc_ExecInitExprRec(fc_e, fc_state,
									&fc_scratch.d.arrayexpr.elemvalues[fc_elemoff],
									&fc_scratch.d.arrayexpr.elemnulls[fc_elemoff]);
					fc_elemoff++;
				}

				/* 然后将所有内容收集到一个数组中 */
				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_RowExpr:
			{
				RowExpr    *fc_rowexpr = (RowExpr *) fc_node;
				int			fc_nelems = list_length(fc_rowexpr->args);
				TupleDesc	fc_tupdesc;
				int			fc_i;
				ListCell   *fc_l;

				/* 构建 tupdesc 来描述结果元组 */
				if (fc_rowexpr->row_typeid == RECORDOID)
				{
					/* 通用记录，使用给定表达式的类型 */
					fc_tupdesc = ExecTypeFromExprList(fc_rowexpr->args);
					/* ...但采用 RowExpr 的列别名 */
					ExecTypeSetColNames(fc_tupdesc, fc_rowexpr->colnames);
					/* 给 tupdesc 祝福，以便以后可以查找 */
					BlessTupleDesc(fc_tupdesc);
				}
				else
				{
					/* 它已被转换为命名类型，使用该类型 */
					fc_tupdesc = lookup_rowtype_tupdesc_copy(fc_rowexpr->row_typeid, -1);
				}

				/*
				 * 在命名类型情况下，tupdesc 可能有比参数列表中更多的列，因为在解析 ROW() 后，类型可能添加了列。我们希望那些额外的列为 NULL，因此我们确保工作区数组足够大，然后将任何额外的列初始化为读取为 NULL。
				 */
				Assert(fc_nelems <= fc_tupdesc->natts);
				fc_nelems = Max(fc_nelems, fc_tupdesc->natts);

				/*
				 * 通过首先为每个字段构建 datum，然后在最后一步形成复合 datum 来评估。
				 */
				fc_scratch.opcode = EEOP_ROW;
				fc_scratch.d.row.tupdesc = fc_tupdesc;

				/* 个别字段 datum 的空间 */
				fc_scratch.d.row.elemvalues =
					(Datum *) palloc(sizeof(Datum) * fc_nelems);
				fc_scratch.d.row.elemnulls =
					(bool *) palloc(sizeof(bool) * fc_nelems);
				/* 如上所述，确保任何额外的列为 NULL */
				memset(fc_scratch.d.row.elemnulls, true, sizeof(bool) * fc_nelems);

				/* 设置评估，跳过任何已删除的列 */
				fc_i = 0;
				foreach(fc_l, fc_rowexpr->args)
				{
					Form_pg_attribute fc_att = TupleDescAttr(fc_tupdesc, fc_i);
					Expr	   *fc_e = (Expr *) lfirst(fc_l);

					if (!fc_att->attisdropped)
					{
						/*
						 * 防止在创建 RowExpr 后对 rowtype 进行 ALTER COLUMN TYPE。 XXX 我们是否也应该检查 typmod？不确定我们能否确保它会相同。
						 */
						if (exprType((Node *) fc_e) != fc_att->atttypid)
							ereport(ERROR,
									(errcode(ERRCODE_DATATYPE_MISMATCH),
									 errmsg("ROW() column has type %s instead of type %s",
											format_type_be(exprType((Node *) fc_e)),
											format_type_be(fc_att->atttypid))));
					}
					else
					{
						/*
						 * 忽略原始表达式并插入一个 NULL。我们实际上并不关心它是什么类型的 NULL，因此总是生成一个 int4 NULL。
						 */
						fc_e = (Expr *) makeNullConst(INT4OID, -1, InvalidOid);
					}

					/* 将列表达式评估到适当的工作区槽中 */
					fc_ExecInitExprRec(fc_e, fc_state,
									&fc_scratch.d.row.elemvalues[fc_i],
									&fc_scratch.d.row.elemnulls[fc_i]);
					fc_i++;
				}

				/* 最后构建行值 */
				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_RowCompareExpr:
			{
				RowCompareExpr *fc_rcexpr = (RowCompareExpr *) fc_node;
				int			fc_nopers = list_length(fc_rcexpr->opnos);
				List	   *fc_adjust_jumps = NIL;
				ListCell   *fc_l_left_expr,
						   *fc_l_right_expr,
						   *fc_l_opno,
						   *fc_l_opfamily,
						   *fc_l_inputcollid;
				ListCell   *fc_lc;

				/*
				 * 遍历每个字段，准备比较。为了处理 NULL 结果，准备跳转到表达式后面。如果比较产生 a != 0 结果，跳转到最终步骤。
				 */
				Assert(list_length(fc_rcexpr->largs) == fc_nopers);
				Assert(list_length(fc_rcexpr->rargs) == fc_nopers);
				Assert(list_length(fc_rcexpr->opfamilies) == fc_nopers);
				Assert(list_length(fc_rcexpr->inputcollids) == fc_nopers);

				forfive(fc_l_left_expr, fc_rcexpr->largs,
						fc_l_right_expr, fc_rcexpr->rargs,
						fc_l_opno, fc_rcexpr->opnos,
						fc_l_opfamily, fc_rcexpr->opfamilies,
						fc_l_inputcollid, fc_rcexpr->inputcollids)
				{
					Expr	   *fc_left_expr = (Expr *) lfirst(fc_l_left_expr);
					Expr	   *fc_right_expr = (Expr *) lfirst(fc_l_right_expr);
					Oid			fc_opno = lfirst_oid(fc_l_opno);
					Oid			fc_opfamily = lfirst_oid(fc_l_opfamily);
					Oid			fc_inputcollid = lfirst_oid(fc_l_inputcollid);
					int			fc_strategy;
					Oid			fc_lefttype;
					Oid			fc_righttype;
					Oid			fc_proc;
					FmgrInfo   *fc_finfo;
					FunctionCallInfo fcinfo;

					get_op_opfamily_properties(fc_opno, fc_opfamily, false,
											   &fc_strategy,
											   &fc_lefttype,
											   &fc_righttype);
					fc_proc = get_opfamily_proc(fc_opfamily,
											 fc_lefttype,
											 fc_righttype,
											 BTORDER_PROC);
					if (!OidIsValid(fc_proc))
						elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
							 BTORDER_PROC, fc_lefttype, fc_righttype, fc_opfamily);

					/* 设置主fmgr查找信息 */
					fc_finfo = palloc0(sizeof(FmgrInfo));
					fcinfo = palloc0(SizeForFunctionCallInfo(2));
					fmgr_info(fc_proc, fc_finfo);
					fmgr_info_set_expr((Node *) fc_node, fc_finfo);
					InitFunctionCallInfoData(*fcinfo, fc_finfo, 2,
											 fc_inputcollid, NULL, NULL);

					/*
					 * 如果我们对索引支持函数强制权限检查，我们需要在这里进行检查。但是索引支持机制并没有这样做，因此这个代码也没有。
					 */

					/* 将左右参数直接评估到 fcinfo 中 */
					fc_ExecInitExprRec(fc_left_expr, fc_state,
									&fcinfo->args[0].value, &fcinfo->args[0].isnull);
					fc_ExecInitExprRec(fc_right_expr, fc_state,
									&fcinfo->args[1].value, &fcinfo->args[1].isnull);

					fc_scratch.opcode = EEOP_ROWCOMPARE_STEP;
					fc_scratch.d.rowcompare_step.finfo = fc_finfo;
					fc_scratch.d.rowcompare_step.fcinfo_data = fcinfo;
					fc_scratch.d.rowcompare_step.fn_addr = fc_finfo->fn_addr;
					/* 跳转目标在下面填充 */
					fc_scratch.d.rowcompare_step.jumpnull = -1;
					fc_scratch.d.rowcompare_step.jumpdone = -1;

					ExprEvalPushStep(fc_state, &fc_scratch);
					fc_adjust_jumps = lappend_int(fc_adjust_jumps,
											   fc_state->steps_len - 1);
				}

				/*
				 * 我们可能有一个零列的 rowtype，在这种情况下，行必然相等比较。
				 */
				if (fc_nopers == 0)
				{
					fc_scratch.opcode = EEOP_CONST;
					fc_scratch.d.constval.value = Int32GetDatum(0);
					fc_scratch.d.constval.isnull = false;
					ExprEvalPushStep(fc_state, &fc_scratch);
				}

				/* 最后，检查最后的比较结果 */
				fc_scratch.opcode = EEOP_ROWCOMPARE_FINAL;
				fc_scratch.d.rowcompare_final.rctype = fc_rcexpr->rctype;
				ExprEvalPushStep(fc_state, &fc_scratch);

				/* 调整跳转目标 */
				foreach(fc_lc, fc_adjust_jumps)
				{
					ExprEvalStep *fc_as = &fc_state->steps[lfirst_int(fc_lc)];

					Assert(fc_as->opcode == EEOP_ROWCOMPARE_STEP);
					Assert(fc_as->d.rowcompare_step.jumpdone == -1);
					Assert(fc_as->d.rowcompare_step.jumpnull == -1);

					/* 跳转到比较评估 */
					fc_as->d.rowcompare_step.jumpdone = fc_state->steps_len - 1;
					/* 跳转到以下表达式 */
					fc_as->d.rowcompare_step.jumpnull = fc_state->steps_len;
				}

				break;
			}

		case T_CoalesceExpr:
			{
				CoalesceExpr *fc_coalesce = (CoalesceExpr *) fc_node;
				List	   *fc_adjust_jumps = NIL;
				ListCell   *fc_lc;

				/* 我们假设至少有一个参数 */
				Assert(fc_coalesce->args != NIL);

				/*
				 * 准备所有合并参数的评估，在每个参数之后推送一个在不为 null 时短路的步骤。
				 */
				foreach(fc_lc, fc_coalesce->args)
				{
					Expr	   *fc_e = (Expr *) lfirst(fc_lc);

					/* 评估参数，直接进入结果 datum */
					fc_ExecInitExprRec(fc_e, fc_state, fc_resv, fc_resnull);

					/* 如果不是 null，则跳至 COALESCE 表达式的末尾 */
					fc_scratch.opcode = EEOP_JUMP_IF_NOT_NULL;
					fc_scratch.d.jump.jumpdone = -1;	/* 以后调整 */
					ExprEvalPushStep(fc_state, &fc_scratch);

					fc_adjust_jumps = lappend_int(fc_adjust_jumps,
											   fc_state->steps_len - 1);
				}

				/* 
				 * 不需要添加一个常量 NULL 返回 - 我们只能在
				 * 已经返回 NULL 的情况下才能到达表达式的末尾。
				 */

				/* 调整跳转目标 */
				foreach(fc_lc, fc_adjust_jumps)
				{
					ExprEvalStep *fc_as = &fc_state->steps[lfirst_int(fc_lc)];

					Assert(fc_as->opcode == EEOP_JUMP_IF_NOT_NULL);
					Assert(fc_as->d.jump.jumpdone == -1);
					fc_as->d.jump.jumpdone = fc_state->steps_len;
				}

				break;
			}

		case T_MinMaxExpr:
			{
				MinMaxExpr *fc_minmaxexpr = (MinMaxExpr *) fc_node;
				int			fc_nelems = list_length(fc_minmaxexpr->args);
				TypeCacheEntry *fc_typentry;
				FmgrInfo   *fc_finfo;
				FunctionCallInfo fcinfo;
				ListCell   *fc_lc;
				int			fc_off;

				/* 查找数据类型的 btree 比较函数 */
				fc_typentry = lookup_type_cache(fc_minmaxexpr->minmaxtype,
											 TYPECACHE_CMP_PROC);
				if (!OidIsValid(fc_typentry->cmp_proc))
					ereport(ERROR,
							(errcode(ERRCODE_UNDEFINED_FUNCTION),
							 errmsg("could not identify a comparison function for type %s",
									format_type_be(fc_minmaxexpr->minmaxtype))));

				/* 
				 * 如果我们对索引支持函数强制检查权限，我们将需要在这里进行检查。
				 * 但是索引支持机制并没有这样做，因此此代码也没有。
				 */

				/* 执行函数查找 */
				fc_finfo = palloc0(sizeof(FmgrInfo));
				fcinfo = palloc0(SizeForFunctionCallInfo(2));
				fmgr_info(fc_typentry->cmp_proc, fc_finfo);
				fmgr_info_set_expr((Node *) fc_node, fc_finfo);
				InitFunctionCallInfoData(*fcinfo, fc_finfo, 2,
										 fc_minmaxexpr->inputcollid, NULL, NULL);

				fc_scratch.opcode = EEOP_MINMAX;
				/* 分配空间以存储参数 */
				fc_scratch.d.minmax.values =
					(Datum *) palloc(sizeof(Datum) * fc_nelems);
				fc_scratch.d.minmax.nulls =
					(bool *) palloc(sizeof(bool) * fc_nelems);
				fc_scratch.d.minmax.nelems = fc_nelems;

				fc_scratch.d.minmax.op = fc_minmaxexpr->op;
				fc_scratch.d.minmax.finfo = fc_finfo;
				fc_scratch.d.minmax.fcinfo_data = fcinfo;

				/* 将表达式求值到 minmax->values/nulls */
				fc_off = 0;
				foreach(fc_lc, fc_minmaxexpr->args)
				{
					Expr	   *fc_e = (Expr *) lfirst(fc_lc);

					fc_ExecInitExprRec(fc_e, fc_state,
									&fc_scratch.d.minmax.values[fc_off],
									&fc_scratch.d.minmax.nulls[fc_off]);
					fc_off++;
				}

				/* 并推送最终比较 */
				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_SQLValueFunction:
			{
				SQLValueFunction *fc_svf = (SQLValueFunction *) fc_node;

				fc_scratch.opcode = EEOP_SQLVALUEFUNCTION;
				fc_scratch.d.sqlvaluefunction.svf = fc_svf;

				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_XmlExpr:
			{
				XmlExpr    *fc_xexpr = (XmlExpr *) fc_node;
				int			fc_nnamed = list_length(fc_xexpr->named_args);
				int			fc_nargs = list_length(fc_xexpr->args);
				int			fc_off;
				ListCell   *fc_arg;

				fc_scratch.opcode = EEOP_XMLEXPR;
				fc_scratch.d.xmlexpr.xexpr = fc_xexpr;

				/* 分配存储所有参数的空间 */
				if (fc_nnamed)
				{
					fc_scratch.d.xmlexpr.named_argvalue =
						(Datum *) palloc(sizeof(Datum) * fc_nnamed);
					fc_scratch.d.xmlexpr.named_argnull =
						(bool *) palloc(sizeof(bool) * fc_nnamed);
				}
				else
				{
					fc_scratch.d.xmlexpr.named_argvalue = NULL;
					fc_scratch.d.xmlexpr.named_argnull = NULL;
				}

				if (fc_nargs)
				{
					fc_scratch.d.xmlexpr.argvalue =
						(Datum *) palloc(sizeof(Datum) * fc_nargs);
					fc_scratch.d.xmlexpr.argnull =
						(bool *) palloc(sizeof(bool) * fc_nargs);
				}
				else
				{
					fc_scratch.d.xmlexpr.argvalue = NULL;
					fc_scratch.d.xmlexpr.argnull = NULL;
				}

				/* 准备参数执行 */
				fc_off = 0;
				foreach(fc_arg, fc_xexpr->named_args)
				{
					Expr	   *fc_e = (Expr *) lfirst(fc_arg);

					fc_ExecInitExprRec(fc_e, fc_state,
									&fc_scratch.d.xmlexpr.named_argvalue[fc_off],
									&fc_scratch.d.xmlexpr.named_argnull[fc_off]);
					fc_off++;
				}

				fc_off = 0;
				foreach(fc_arg, fc_xexpr->args)
				{
					Expr	   *fc_e = (Expr *) lfirst(fc_arg);

					fc_ExecInitExprRec(fc_e, fc_state,
									&fc_scratch.d.xmlexpr.argvalue[fc_off],
									&fc_scratch.d.xmlexpr.argnull[fc_off]);
					fc_off++;
				}

				/* 并评估实际的 XML 表达式 */
				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_NullTest:
			{
				NullTest   *fc_ntest = (NullTest *) fc_node;

				if (fc_ntest->nulltesttype == IS_NULL)
				{
					if (fc_ntest->argisrow)
						fc_scratch.opcode = EEOP_NULLTEST_ROWISNULL;
					else
						fc_scratch.opcode = EEOP_NULLTEST_ISNULL;
				}
				else if (fc_ntest->nulltesttype == IS_NOT_NULL)
				{
					if (fc_ntest->argisrow)
						fc_scratch.opcode = EEOP_NULLTEST_ROWISNOTNULL;
					else
						fc_scratch.opcode = EEOP_NULLTEST_ISNOTNULL;
				}
				else
				{
					elog(ERROR, "unrecognized nulltesttype: %d",
						 (int) fc_ntest->nulltesttype);
				}
				/* 初始化缓存以防这是一个行测试 */
				fc_scratch.d.nulltest_row.rowcache.cacheptr = NULL;

				/* 首先将参数评估到结果变量中 */
				fc_ExecInitExprRec(fc_ntest->arg, fc_state,
								fc_resv, fc_resnull);

				/* 然后推送该参数的测试 */
				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_BooleanTest:
			{
				BooleanTest *fc_btest = (BooleanTest *) fc_node;

				/* 
				 * 直接将参数评估到结果数据中。这没问题，
				 * 因为 resv/resnull 确实不会在其他地方使用，
				 * 并且会被以下 EEOP_BOOLTEST_IS_* 步骤覆盖。
				 */
				fc_ExecInitExprRec(fc_btest->arg, fc_state, fc_resv, fc_resnull);

				switch (fc_btest->booltesttype)
				{
					case IS_TRUE:
						fc_scratch.opcode = EEOP_BOOLTEST_IS_TRUE;
						break;
					case IS_NOT_TRUE:
						fc_scratch.opcode = EEOP_BOOLTEST_IS_NOT_TRUE;
						break;
					case IS_FALSE:
						fc_scratch.opcode = EEOP_BOOLTEST_IS_FALSE;
						break;
					case IS_NOT_FALSE:
						fc_scratch.opcode = EEOP_BOOLTEST_IS_NOT_FALSE;
						break;
					case IS_UNKNOWN:
						/* 与标量 IS NULL 测试相同 */
						fc_scratch.opcode = EEOP_NULLTEST_ISNULL;
						break;
					case IS_NOT_UNKNOWN:
						/* 与标量 IS NOT NULL 测试相同 */
						fc_scratch.opcode = EEOP_NULLTEST_ISNOTNULL;
						break;
					default:
						elog(ERROR, "unrecognized booltesttype: %d",
							 (int) fc_btest->booltesttype);
				}

				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_CoerceToDomain:
			{
				CoerceToDomain *fc_ctest = (CoerceToDomain *) fc_node;

				fc_ExecInitCoerceToDomain(&fc_scratch, fc_ctest, fc_state,
									   fc_resv, fc_resnull);
				break;
			}

		case T_CoerceToDomainValue:
			{
				/* 
				 * 从 innermost_domainval 所识别的位置读取。注意
				 * 如果我们正在编译一个独立的域检查而不是嵌入在
				 * 更大的表达式中，则 innermost_domainval 可能为 NULL。
				 * 在这种情况下，我们必须从 econtext->domainValue_datum 中读取。
				 * 我们将在运行时处理这种情况。
				 */
				fc_scratch.opcode = EEOP_DOMAIN_TESTVAL;
				/* 我们与 case testval 共享指令联合变体 */
				fc_scratch.d.casetest.value = fc_state->innermost_domainval;
				fc_scratch.d.casetest.isnull = fc_state->innermost_domainnull;

				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_CurrentOfExpr:
			{
				fc_scratch.opcode = EEOP_CURRENTOFEXPR;
				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		case T_NextValueExpr:
			{
				NextValueExpr *fc_nve = (NextValueExpr *) fc_node;

				fc_scratch.opcode = EEOP_NEXTVALUEEXPR;
				fc_scratch.d.nextvalueexpr.seqid = fc_nve->seqid;
				fc_scratch.d.nextvalueexpr.seqtypid = fc_nve->typeId;

				ExprEvalPushStep(fc_state, &fc_scratch);
				break;
			}

		default:
			elog(ERROR, "unrecognized node type: %d",
				 (int) nodeTag(fc_node));
			break;
	}
}

/* 
 * 向 ExprState->steps 添加另一个表达式评估步骤。
 *
 * 请注意，这可能会重新分配 es->steps，因此在表达式仍在构建时，
 * 不得使用指向该数组的任何指针。
 */
void ExprEvalPushStep(ExprState *fc_es, const ExprEvalStep *fc_s)
{
	if (fc_es->steps_alloc == 0)
	{
		fc_es->steps_alloc = 16;
		fc_es->steps = palloc(sizeof(ExprEvalStep) * fc_es->steps_alloc);
	}
	else if (fc_es->steps_alloc == fc_es->steps_len)
	{
		fc_es->steps_alloc *= 2;
		fc_es->steps = repalloc(fc_es->steps,
							 sizeof(ExprEvalStep) * fc_es->steps_alloc);
	}

	memcpy(&fc_es->steps[fc_es->steps_len++], fc_s, sizeof(ExprEvalStep));
}

/* 
 * 执行评估函数式表达式所需的设置，将参数评估步骤附加
 * 到 *state 的步骤列表中，并设置 *scratch 以准备推送。
 *
 * *scratch 在这里不被推送，以便调用者可以覆盖操作码，
 * 这对于如 DISTINCT 这样的函数式情况非常有用。
 */
static void fc_ExecInitFunc(ExprEvalStep *fc_scratch, Expr *fc_node, List *fc_args, Oid fc_funcid,
			 Oid fc_inputcollid, ExprState *fc_state)
{
	int			fc_nargs = list_length(fc_args);
	AclResult	fc_aclresult;
	FmgrInfo   *fc_flinfo;
	FunctionCallInfo fcinfo;
	int			fc_argno;
	ListCell   *fc_lc;

	/* 检查调用函数的权限 */
	fc_aclresult = pg_proc_aclcheck(fc_funcid, GetUserId(), ACL_EXECUTE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_FUNCTION, get_func_name(fc_funcid));
	InvokeFunctionExecuteHook(fc_funcid);

	/* 
	 * 对 nargs 的安全检查。在正常情况下，这绝对不应该
	 * 失败，因为解析器应该更早检查。但是，可能它会失败，如果
	 * 服务器已编译的 FUNC_MAX_ARGS 小于 pg_proc 中声明的某些函数？
	 */
	if (fc_nargs > FUNC_MAX_ARGS)
		ereport(ERROR,
				(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
				 errmsg_plural("cannot pass more than %d argument to a function",
							   "cannot pass more than %d arguments to a function",
							   FUNC_MAX_ARGS,
							   FUNC_MAX_ARGS)));

	/* 为此调用分配函数查找数据和参数工作空间 */
	fc_scratch->d.func.finfo = palloc0(sizeof(FmgrInfo));
	fc_scratch->d.func.fcinfo_data = palloc0(SizeForFunctionCallInfo(fc_nargs));
	fc_flinfo = fc_scratch->d.func.finfo;
	fcinfo = fc_scratch->d.func.fcinfo_data;

	/* 设置主fmgr查找信息 */
	fmgr_info(fc_funcid, fc_flinfo);
	fmgr_info_set_expr((Node *) fc_node, fc_flinfo);

	/* 也初始化函数调用参数结构 */
	InitFunctionCallInfoData(*fcinfo, fc_flinfo,
							 fc_nargs, fc_inputcollid, NULL, NULL);

	/* 保留此信息的额外副本以节省运行时的间接调用 */
	fc_scratch->d.func.fn_addr = fc_flinfo->fn_addr;
	fc_scratch->d.func.nargs = fc_nargs;

	/* 我们在这里仅支持非集合函数 */
	if (fc_flinfo->fn_retset)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("set-valued function called in context that cannot accept a set"),
				 fc_state->parent ?
				 executor_errposition(fc_state->parent->state,
									  exprLocation((Node *) fc_node)) : 0));

	/* 构建代码以直接评估参数到 fcinfo 结构中 */
	fc_argno = 0;
	foreach(fc_lc, fc_args)
	{
		Expr	   *fc_arg = (Expr *) lfirst(fc_lc);

		if (IsA(fc_arg, Const))
		{
			/* 
			 * 不要在每一轮评估常量参数；对于比较中的常量尤其
			 * 有趣。
			 */
			Const	   *fc_con = (Const *) fc_arg;

			fcinfo->args[fc_argno].value = fc_con->constvalue;
			fcinfo->args[fc_argno].isnull = fc_con->constisnull;
		}
		else
		{
			fc_ExecInitExprRec(fc_arg, fc_state,
							&fcinfo->args[fc_argno].value,
							&fcinfo->args[fc_argno].isnull);
		}
		fc_argno++;
	}

	/* 根据严格性和统计级别插入适当的操作码 */
	if (pgstat_track_functions <= fc_flinfo->fn_stats)
	{
		if (fc_flinfo->fn_strict && fc_nargs > 0)
			fc_scratch->opcode = EEOP_FUNCEXPR_STRICT;
		else
			fc_scratch->opcode = EEOP_FUNCEXPR;
	}
	else
	{
		if (fc_flinfo->fn_strict && fc_nargs > 0)
			fc_scratch->opcode = EEOP_FUNCEXPR_STRICT_FUSAGE;
		else
			fc_scratch->opcode = EEOP_FUNCEXPR_FUSAGE;
	}
}

/*
 * 添加执行步骤，执行所需的设置，这些设置是在表达式的主要执行之前进行的。
 */
static void fc_ExecCreateExprSetupSteps(ExprState *fc_state, Node *fc_node)
{
	ExprSetupInfo fc_info = {0, 0, 0, NIL};

	/* 预扫描以找出我们需要什么。 */
	fc_expr_setup_walker(fc_node, &fc_info);

	/* 并生成这些步骤。 */
	fc_ExecPushExprSetupSteps(fc_state, &fc_info);
}

/*
 * 添加执行表达式设置的步骤，如“信息”所示。
 * 这在构建覆盖多个表达式的 ExprState 时非常有用。
 */
static void fc_ExecPushExprSetupSteps(ExprState *fc_state, ExprSetupInfo *fc_info)
{
	ExprEvalStep fc_scratch = {0};
	ListCell   *fc_lc;

	fc_scratch.resvalue = NULL;
	fc_scratch.resnull = NULL;

	/*
	 * 添加步骤，按照表达式中出现的任何变量的要求变形 ExprState 的内部/外部/扫描槽。
	 */
	if (fc_info->last_inner > 0)
	{
		fc_scratch.opcode = EEOP_INNER_FETCHSOME;
		fc_scratch.d.fetch.last_var = fc_info->last_inner;
		fc_scratch.d.fetch.fixed = false;
		fc_scratch.d.fetch.kind = NULL;
		fc_scratch.d.fetch.known_desc = NULL;
		if (fc_ExecComputeSlotInfo(fc_state, &fc_scratch))
			ExprEvalPushStep(fc_state, &fc_scratch);
	}
	if (fc_info->last_outer > 0)
	{
		fc_scratch.opcode = EEOP_OUTER_FETCHSOME;
		fc_scratch.d.fetch.last_var = fc_info->last_outer;
		fc_scratch.d.fetch.fixed = false;
		fc_scratch.d.fetch.kind = NULL;
		fc_scratch.d.fetch.known_desc = NULL;
		if (fc_ExecComputeSlotInfo(fc_state, &fc_scratch))
			ExprEvalPushStep(fc_state, &fc_scratch);
	}
	if (fc_info->last_scan > 0)
	{
		fc_scratch.opcode = EEOP_SCAN_FETCHSOME;
		fc_scratch.d.fetch.last_var = fc_info->last_scan;
		fc_scratch.d.fetch.fixed = false;
		fc_scratch.d.fetch.kind = NULL;
		fc_scratch.d.fetch.known_desc = NULL;
		if (fc_ExecComputeSlotInfo(fc_state, &fc_scratch))
			ExprEvalPushStep(fc_state, &fc_scratch);
	}

	/*
	 * 添加步骤以执行表达式中出现的任何 MULTIEXPR 子计划。
	 * 我们需要在使用任何引用其输出的参数之前评估这些，但在我们为它们可能包含的任何变量引用做好准备之后。
	 * （MULTIEXPR 子计划之间不能有交叉引用，因此我们不必担心它们的顺序。）
	 */
	foreach(fc_lc, fc_info->multiexpr_subplans)
	{
		SubPlan    *fc_subplan = (SubPlan *) lfirst(fc_lc);
		SubPlanState *fc_sstate;

		Assert(fc_subplan->subLinkType == MULTIEXPR_SUBLINK);

		/* 这应与 ExecInitExprRec 为其他子计划所做的匹配： */

		if (!fc_state->parent)
			elog(ERROR, "SubPlan found with no parent plan");

		fc_sstate = ExecInitSubPlan(fc_subplan, fc_state->parent);

		/* 将SubPlanState节点添加到state->parent->subPlan */
		fc_state->parent->subPlan = lappend(fc_state->parent->subPlan,
										 fc_sstate);

		fc_scratch.opcode = EEOP_SUBPLAN;
		fc_scratch.d.subplan.sstate = fc_sstate;

		/* 结果可以被忽略，但我们最好把它放到某个地方。 */
		fc_scratch.resvalue = &fc_state->resvalue;
		fc_scratch.resnull = &fc_state->resnull;

		ExprEvalPushStep(fc_state, &fc_scratch);
	}
}

/*
 * expr_setup_walker：ExecCreateExprSetupSteps 的表达式走访器
 */
static bool fc_expr_setup_walker(Node *fc_node, ExprSetupInfo *fc_info)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, Var))
	{
		Var		   *fc_variable = (Var *) fc_node;
		AttrNumber	fc_attnum = fc_variable->varattno;

		switch (fc_variable->varno)
		{
			case INNER_VAR:
				fc_info->last_inner = Max(fc_info->last_inner, fc_attnum);
				break;

			case OUTER_VAR:
				fc_info->last_outer = Max(fc_info->last_outer, fc_attnum);
				break;

				/* INDEX_VAR 由默认情况处理 */

			default:
				fc_info->last_scan = Max(fc_info->last_scan, fc_attnum);
				break;
		}
		return false;
	}

	/* 也收集所有 MULTIEXPR 子计划 */
	if (IsA(fc_node, SubPlan))
	{
		SubPlan    *fc_subplan = (SubPlan *) fc_node;

		if (fc_subplan->subLinkType == MULTIEXPR_SUBLINK)
			fc_info->multiexpr_subplans = lappend(fc_info->multiexpr_subplans,
											   fc_subplan);
	}

	/*
	 * 不要检查 Aggrefs 或 WindowFuncs 的参数或过滤器，
	 * 因为这些不代表在调用表达式的 econtext 内部评估的表达式。
	 * GroupingFunc 参数根本不会被评估。
	 */
	if (IsA(fc_node, Aggref))
		return false;
	if (IsA(fc_node, WindowFunc))
		return false;
	if (IsA(fc_node, GroupingFunc))
		return false;
	return expression_tree_walker(fc_node, fc_expr_setup_walker,
								  (void *) fc_info);
}

/*
 * 计算 EEOP_*_FETCHSOME 操作的附加信息。
 *
 * 目标是确定一个槽是否是“固定的”，即每次表达式评估将具有相同类型的槽，
 * 具有等效的描述符。
 *
 * 如果需要变形步骤，则返回 true，否则返回 false。
 */
static bool fc_ExecComputeSlotInfo(ExprState *fc_state, ExprEvalStep *fc_op)
{
	PlanState  *fc_parent = fc_state->parent;
	TupleDesc	fc_desc = NULL;
	const TupleTableSlotOps *fc_tts_ops = NULL;
	bool		fc_isfixed = false;
	ExprEvalOp	fc_opcode = fc_op->opcode;

	Assert(fc_opcode == EEOP_INNER_FETCHSOME ||
		   fc_opcode == EEOP_OUTER_FETCHSOME ||
		   fc_opcode == EEOP_SCAN_FETCHSOME);

	if (fc_op->d.fetch.known_desc != NULL)
	{
		fc_desc = fc_op->d.fetch.known_desc;
		fc_tts_ops = fc_op->d.fetch.kind;
		fc_isfixed = fc_op->d.fetch.kind != NULL;
	}
	else if (!fc_parent)
	{
		fc_isfixed = false;
	}
	else if (fc_opcode == EEOP_INNER_FETCHSOME)
	{
		PlanState  *fc_is = innerPlanState(fc_parent);

		if (fc_parent->inneropsset && !fc_parent->inneropsfixed)
		{
			fc_isfixed = false;
		}
		else if (fc_parent->inneropsset && fc_parent->innerops)
		{
			fc_isfixed = true;
			fc_tts_ops = fc_parent->innerops;
			fc_desc = ExecGetResultType(fc_is);
		}
		else if (fc_is)
		{
			fc_tts_ops = ExecGetResultSlotOps(fc_is, &fc_isfixed);
			fc_desc = ExecGetResultType(fc_is);
		}
	}
	else if (fc_opcode == EEOP_OUTER_FETCHSOME)
	{
		PlanState  *fc_os = outerPlanState(fc_parent);

		if (fc_parent->outeropsset && !fc_parent->outeropsfixed)
		{
			fc_isfixed = false;
		}
		else if (fc_parent->outeropsset && fc_parent->outerops)
		{
			fc_isfixed = true;
			fc_tts_ops = fc_parent->outerops;
			fc_desc = ExecGetResultType(fc_os);
		}
		else if (fc_os)
		{
			fc_tts_ops = ExecGetResultSlotOps(fc_os, &fc_isfixed);
			fc_desc = ExecGetResultType(fc_os);
		}
	}
	else if (fc_opcode == EEOP_SCAN_FETCHSOME)
	{
		fc_desc = fc_parent->scandesc;

		if (fc_parent->scanops)
			fc_tts_ops = fc_parent->scanops;

		if (fc_parent->scanopsset)
			fc_isfixed = fc_parent->scanopsfixed;
	}

	if (fc_isfixed && fc_desc != NULL && fc_tts_ops != NULL)
	{
		fc_op->d.fetch.fixed = true;
		fc_op->d.fetch.kind = fc_tts_ops;
		fc_op->d.fetch.known_desc = fc_desc;
	}
	else
	{
		fc_op->d.fetch.fixed = false;
		fc_op->d.fetch.kind = NULL;
		fc_op->d.fetch.known_desc = NULL;
	}

	/* 如果槽已知始终是虚拟的，我们永远不需要变形 */
	if (fc_op->d.fetch.fixed && fc_op->d.fetch.kind == &TTSOpsVirtual)
		return false;

	return true;
}

/*
 * 准备对整个行变量的评估步骤。
 * 呼叫者仍然需要推送该步骤。
 */
static void fc_ExecInitWholeRowVar(ExprEvalStep *fc_scratch, Var *fc_variable, ExprState *fc_state)
{
	PlanState  *fc_parent = fc_state->parent;

	/* 填充所有但目标 */
	fc_scratch->opcode = EEOP_WHOLEROW;
	fc_scratch->d.wholerow.var = fc_variable;
	fc_scratch->d.wholerow.first = true;
	fc_scratch->d.wholerow.slow = false;
	fc_scratch->d.wholerow.tupdesc = NULL; /* 在运行时填充 */
	fc_scratch->d.wholerow.junkFilter = NULL;

	/*
	 * 如果输入元组来自子查询，它可能包含“resjunk”列（例如 GROUP BY 或 ORDER BY 列），
	 * 我们不想在整个行结果中保留这些列。
	 * 我们可以通过将元组通过 JunkFilter 来去掉这些列——但是要制作一个，我们必须获取
	 * 子查询的目标列表。幸运的是，这种情况并不多，我们可以通过检查我们的父 PlanState
	 * 来识别所有这些情况。我们假设在没有父计划的独立表达式中这不是问题。
	 * （整个行变量可以出现在这样的表达式中，但它们将始终引用表行。）
	 */
	if (fc_parent)
	{
		PlanState  *fc_subplan = NULL;

		switch (nodeTag(fc_parent))
		{
			case T_SubqueryScanState:
				fc_subplan = ((SubqueryScanState *) fc_parent)->subplan;
				break;
			case T_CteScanState:
				fc_subplan = ((CteScanState *) fc_parent)->cteplanstate;
				break;
			default:
				break;
		}

		if (fc_subplan)
		{
			bool		fc_junk_filter_needed = false;
			ListCell   *fc_tlist;

			/* 检测子计划的目标列表是否确实有任何垃圾列 */
			foreach(fc_tlist, fc_subplan->plan->targetlist)
			{
				TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_tlist);

				if (fc_tle->resjunk)
				{
					fc_junk_filter_needed = true;
					break;
				}
			}

			/* 如果有，现在构建垃圾过滤器 */
			if (fc_junk_filter_needed)
			{
				fc_scratch->d.wholerow.junkFilter =
					ExecInitJunkFilter(fc_subplan->plan->targetlist,
									   ExecInitExtraTupleSlot(fc_parent->state, NULL,
															  &TTSOpsVirtual));
			}
		}
	}
}

/*
 * 准备对 SubscriptingRef 表达式的评估。
 */
static void fc_ExecInitSubscriptingRef(ExprEvalStep *fc_scratch, SubscriptingRef *fc_sbsref,
						ExprState *fc_state, Datum *fc_resv, bool *fc_resnull)
{
	bool		fc_isAssignment = (fc_sbsref->refassgnexpr != NULL);
	int			fc_nupper = list_length(fc_sbsref->refupperindexpr);
	int			fc_nlower = list_length(fc_sbsref->reflowerindexpr);
	const SubscriptRoutines *fc_sbsroutines;
	SubscriptingRefState *fc_sbsrefstate;
	SubscriptExecSteps fc_methods;
	char	   *fc_ptr;
	List	   *fc_adjust_jumps = NIL;
	ListCell   *fc_lc;
	int			fc_i;

	/* 查找下标支持方法 */
	fc_sbsroutines = getSubscriptingRoutines(fc_sbsref->refcontainertype, NULL);
	if (!fc_sbsroutines)
#ifdef FDD //cppcheck
	{//cppcheck
#endif
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("cannot subscript type %s because it does not support subscripting",
						format_type_be(fc_sbsref->refcontainertype)),
				 fc_state->parent ?
				 executor_errposition(fc_state->parent->state,
									  exprLocation((Node *) fc_sbsref)) : 0));
#ifdef FDD //cppcheck
		return;
	}
#endif
	/* 分配 sbsrefstate，具有足够的空间用于每个下标数组 */
	fc_sbsrefstate = palloc0(MAXALIGN(sizeof(SubscriptingRefState)) +
						  (fc_nupper + fc_nlower) * (sizeof(Datum) +
											   2 * sizeof(bool)));

	/* 填充 SubscriptingRefState 的常量字段 */
	fc_sbsrefstate->isassignment = fc_isAssignment;
	fc_sbsrefstate->numupper = fc_nupper;
	fc_sbsrefstate->numlower = fc_nlower;
	/* 设置每个下标数组 */
	fc_ptr = ((char *) fc_sbsrefstate) + MAXALIGN(sizeof(SubscriptingRefState));
	fc_sbsrefstate->upperindex = (Datum *) fc_ptr;
	fc_ptr += fc_nupper * sizeof(Datum);
	fc_sbsrefstate->lowerindex = (Datum *) fc_ptr;
	fc_ptr += fc_nlower * sizeof(Datum);
	fc_sbsrefstate->upperprovided = (bool *) fc_ptr;
	fc_ptr += fc_nupper * sizeof(bool);
	fc_sbsrefstate->lowerprovided = (bool *) fc_ptr;
	fc_ptr += fc_nlower * sizeof(bool);
	fc_sbsrefstate->upperindexnull = (bool *) fc_ptr;
	fc_ptr += fc_nupper * sizeof(bool);
	fc_sbsrefstate->lowerindexnull = (bool *) fc_ptr;
	/* ptr += nlower * sizeof(bool); */

	/*
	 * 让容器类型特定的代码有机会。它必须填充
	 * “methods” 结构以便我们可以在下面的执行步骤中可能使用的函数指针；
	 * 它还可以选择性地设置一些指向工作区字段的数据。
	 */
	memset(&fc_methods, 0, sizeof(fc_methods));
	fc_sbsroutines->exec_setup(fc_sbsref, fc_sbsrefstate, &fc_methods);

	/*
	 * 评估数组输入。将其安全地放入 resv/resnull 中是安全的，因为我们
	 * 不会将其用作任何其他子表达式的目标，且它将
	 * 被最终的 EEOP_SBSREF_FETCH/ASSIGN 步骤覆盖，后者是最后推送的。
	 */
	fc_ExecInitExprRec(fc_sbsref->refexpr, fc_state, fc_resv, fc_resnull);

	/*
	 * 如果 refexpr 返回 NULL，并且该操作应该是严格的，则结果为 NULL。
	 * 我们可以仅通过 JUMP_IF_NULL 来实现这一点，因为我们
	 * 已将数组评估到所需的目标位置。
	 */
	if (!fc_isAssignment && fc_sbsroutines->fetch_strict)
	{
		fc_scratch->opcode = EEOP_JUMP_IF_NULL;
		fc_scratch->d.jump.jumpdone = -1;	/* 以后调整 */
		ExprEvalPushStep(fc_state, fc_scratch);
		fc_adjust_jumps = lappend_int(fc_adjust_jumps,
								   fc_state->steps_len - 1);
	}

	/* 评估上标 */
	fc_i = 0;
	foreach(fc_lc, fc_sbsref->refupperindexpr)
	{
		Expr	   *fc_e = (Expr *) lfirst(fc_lc);

		/* 在切片时，单个下标边界可以省略 */
		if (!fc_e)
		{
			fc_sbsrefstate->upperprovided[fc_i] = false;
			fc_sbsrefstate->upperindexnull[fc_i] = true;
		}
		else
		{
			fc_sbsrefstate->upperprovided[fc_i] = true;
			/* 每个下标被计算为适当的数组条目 */
			fc_ExecInitExprRec(fc_e, fc_state,
							&fc_sbsrefstate->upperindex[fc_i],
							&fc_sbsrefstate->upperindexnull[fc_i]);
		}
		fc_i++;
	}

	/* 以类似方式评估较低的下标 */
	fc_i = 0;
	foreach(fc_lc, fc_sbsref->reflowerindexpr)
	{
		Expr	   *fc_e = (Expr *) lfirst(fc_lc);

		/* 在切片时，单个下标边界可以省略 */
		if (!fc_e)
		{
			fc_sbsrefstate->lowerprovided[fc_i] = false;
			fc_sbsrefstate->lowerindexnull[fc_i] = true;
		}
		else
		{
			fc_sbsrefstate->lowerprovided[fc_i] = true;
			/* 每个下标被计算为适当的数组条目 */
			fc_ExecInitExprRec(fc_e, fc_state,
							&fc_sbsrefstate->lowerindex[fc_i],
							&fc_sbsrefstate->lowerindexnull[fc_i]);
		}
		fc_i++;
	}

	/* SBSREF_SUBSCRIPTS 一次检查并转换所有下标 */
	if (fc_methods.sbs_check_subscripts)
	{
		fc_scratch->opcode = EEOP_SBSREF_SUBSCRIPTS;
		fc_scratch->d.sbsref_subscript.subscriptfunc = fc_methods.sbs_check_subscripts;
		fc_scratch->d.sbsref_subscript.state = fc_sbsrefstate;
		fc_scratch->d.sbsref_subscript.jumpdone = -1;	/* 以后调整 */
		ExprEvalPushStep(fc_state, fc_scratch);
		fc_adjust_jumps = lappend_int(fc_adjust_jumps,
								   fc_state->steps_len - 1);
	}

	if (fc_isAssignment)
	{
		Datum	   *fc_save_innermost_caseval;
		bool	   *fc_save_innermost_casenull;

		/* 检查未实现的方法 */
		if (!fc_methods.sbs_assign)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("type %s does not support subscripted assignment",
							format_type_be(fc_sbsref->refcontainertype))));

		/*
		 * 我们可能有一个嵌套赋值的情况，其中
		 * refassgnexpr 本身就是一个 FieldStore 或 SubscriptingRef，需要
		 * 获取并修改被替换的数组元素或切片的先前值。如果是这样的话，
		 * 我们必须从数组中提取该值，并通过 CaseTestExpr 机制传递下去。
		 * 由于在这里和需要该值的地方之间不能有 CASE，因此复用 CASE 机制是安全的，
		 * 数组赋值也不能在 CASE 内部。（所以保存和恢复 innermost_caseval 只是出于过度谨慎，
		 * 但让我们还是这样做。）
		 *
		 * 由于获取旧元素可能是一个非平凡的开销，因此仅在需要时进行。
		 */
		if (fc_isAssignmentIndirectionExpr(fc_sbsref->refassgnexpr))
		{
			if (!fc_methods.sbs_fetch_old)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("type %s does not support subscripted assignment",
								format_type_be(fc_sbsref->refcontainertype))));
			fc_scratch->opcode = EEOP_SBSREF_OLD;
			fc_scratch->d.sbsref.subscriptfunc = fc_methods.sbs_fetch_old;
			fc_scratch->d.sbsref.state = fc_sbsrefstate;
			ExprEvalPushStep(fc_state, fc_scratch);
		}

		/* SBSREF_OLD 将提取的值放入 prevvalue/prevnull */
		fc_save_innermost_caseval = fc_state->innermost_caseval;
		fc_save_innermost_casenull = fc_state->innermost_casenull;
		fc_state->innermost_caseval = &fc_sbsrefstate->prevvalue;
		fc_state->innermost_casenull = &fc_sbsrefstate->prevnull;

		/* 将替换值评估为 replacevalue/replacenull */
		fc_ExecInitExprRec(fc_sbsref->refassgnexpr, fc_state,
						&fc_sbsrefstate->replacevalue, &fc_sbsrefstate->replacenull);

		fc_state->innermost_caseval = fc_save_innermost_caseval;
		fc_state->innermost_casenull = fc_save_innermost_casenull;

		/* 并执行赋值 */
		fc_scratch->opcode = EEOP_SBSREF_ASSIGN;
		fc_scratch->d.sbsref.subscriptfunc = fc_methods.sbs_assign;
		fc_scratch->d.sbsref.state = fc_sbsrefstate;
		ExprEvalPushStep(fc_state, fc_scratch);
	}
	else
	{
		/* 数组提取要简单得多 */
		fc_scratch->opcode = EEOP_SBSREF_FETCH;
		fc_scratch->d.sbsref.subscriptfunc = fc_methods.sbs_fetch;
		fc_scratch->d.sbsref.state = fc_sbsrefstate;
		ExprEvalPushStep(fc_state, fc_scratch);
	}

	/* 调整跳转目标 */
	foreach(fc_lc, fc_adjust_jumps)
	{
		ExprEvalStep *fc_as = &fc_state->steps[lfirst_int(fc_lc)];

		if (fc_as->opcode == EEOP_SBSREF_SUBSCRIPTS)
		{
			Assert(fc_as->d.sbsref_subscript.jumpdone == -1);
			fc_as->d.sbsref_subscript.jumpdone = fc_state->steps_len;
		}
		else
		{
			Assert(fc_as->opcode == EEOP_JUMP_IF_NULL);
			Assert(fc_as->d.jump.jumpdone == -1);
			fc_as->d.jump.jumpdone = fc_state->steps_len;
		}
	}
}

/*
 * 为评估 SubscriptingRef 表达式准备的助手：expr
 * 是一个需要传递旧元素值的嵌套 FieldStore 或 SubscriptingRef 吗？
 *
 * （我们也可以在 FieldStore 中使用这个，但在那种情况下，传递旧值是非常简单的，
 * 所以没有必要。）
 *
 * 注意：这似乎需要递归，但在大多数情况下并不需要；
 * CaseTestExpr（如果有的话）将直接作为顶层节点的 arg 或 refexpr。
 * 嵌套赋值情况产生的表达式树中，每级赋值都有自己的 CaseTestExpr，
 * 而递归结构出现在 newvals 或 refassgnexpr 字段中。
 * 不过有个例外：如果数组是域数组，我们将有一个 CoerceToDomain 或 RelabelType 作为 refassgnexpr，
 * 我们需要能够透视它。
 */
static bool fc_isAssignmentIndirectionExpr(Expr *fc_expr)
{
	if (fc_expr == NULL)
		return false;			/* 只是 parano */
	if (IsA(fc_expr, FieldStore))
	{
		FieldStore *fc_fstore = (FieldStore *) fc_expr;

		if (fc_fstore->arg && IsA(fc_fstore->arg, CaseTestExpr))
			return true;
	}
	else if (IsA(fc_expr, SubscriptingRef))
	{
		SubscriptingRef *fc_sbsRef = (SubscriptingRef *) fc_expr;

		if (fc_sbsRef->refexpr && IsA(fc_sbsRef->refexpr, CaseTestExpr))
			return true;
	}
	else if (IsA(fc_expr, CoerceToDomain))
	{
		CoerceToDomain *fc_cd = (CoerceToDomain *) fc_expr;

		return fc_isAssignmentIndirectionExpr(fc_cd->arg);
	}
	else if (IsA(fc_expr, RelabelType))
	{
		RelabelType *fc_r = (RelabelType *) fc_expr;

		return fc_isAssignmentIndirectionExpr(fc_r->arg);
	}
	return false;
}

/*
 * 准备评估 CoerceToDomain 表达式。
 */
static void fc_ExecInitCoerceToDomain(ExprEvalStep *fc_scratch, CoerceToDomain *fc_ctest,
					   ExprState *fc_state, Datum *fc_resv, bool *fc_resnull)
{
	DomainConstraintRef *fc_constraint_ref;
	Datum	   *fc_domainval = NULL;
	bool	   *fc_domainnull = NULL;
	ListCell   *fc_l;

	fc_scratch->d.domaincheck.resulttype = fc_ctest->resulttype;
	/* 只有在需要时我们才会分配工作空间 */
	fc_scratch->d.domaincheck.checkvalue = NULL;
	fc_scratch->d.domaincheck.checknull = NULL;

	/*
	 * 评估参数 - 直接将其存储到 resv/resnull 中是可以的，
	 * 如果存在约束失败将会有错误，否则这是需要返回的值。
	 */
	fc_ExecInitExprRec(fc_ctest->arg, fc_state, fc_resv, fc_resnull);

	/*
	 * 注意：如果参数是 varlena 类型，它可能是一个读/写扩展对象。
	 * 我们想要将 R/W 指针作为最终结果返回，但必须将 R/O 指针作为值传递给
	 * 任何在检查表达式中的函数。
	 * 不过，除非实际上有至少一个检查表达式，否则我们不会发出 MAKE_READONLY 步骤。
	 * 在我们测试之前，domainval/domainnull 为 NULL。
	 */

	/*
	 * 收集与域相关的约束。
	 *
	 * 注意：在 PG v10 之前，我们会在每次评估表达式时重新检查约束集。
	 * 现在我们在执行器初始化期间将它们构建到 ExprState 中。
	 * 这意味着我们不需要 typcache.c 提供编译后的表达式。
	 */
	fc_constraint_ref = (DomainConstraintRef *)
		palloc(sizeof(DomainConstraintRef));
	InitDomainConstraintRef(fc_ctest->resulttype,
							fc_constraint_ref,
							CurrentMemoryContext,
							false);

	/*
	 * 编译代码以检查每个域约束。 NOTNULL 约束可以
	 * 直接应用于 resv/resnull 值，但对于 CHECK 约束我们
	 * 需要更多的拼接。
	 */
	foreach(fc_l, fc_constraint_ref->constraints)
	{
		DomainConstraintState *fc_con = (DomainConstraintState *) lfirst(fc_l);
		Datum	   *fc_save_innermost_domainval;
		bool	   *fc_save_innermost_domainnull;

		fc_scratch->d.domaincheck.constraintname = fc_con->name;

		switch (fc_con->constrainttype)
		{
			case DOM_CONSTRAINT_NOTNULL:
				fc_scratch->opcode = EEOP_DOMAIN_NOTNULL;
				ExprEvalPushStep(fc_state, fc_scratch);
				break;
			case DOM_CONSTRAINT_CHECK:
				/* 如果我们还没有分配，为 CHECK 输出分配工作空间 */
				if (fc_scratch->d.domaincheck.checkvalue == NULL)
				{
					fc_scratch->d.domaincheck.checkvalue =
						(Datum *) palloc(sizeof(Datum));
					fc_scratch->d.domaincheck.checknull =
						(bool *) palloc(sizeof(bool));
				}

				/*
				 * 如果第一次执行，确定 CoerceToDomainValue
				 * 节点应该从哪里读取。
				 */
				if (fc_domainval == NULL)
				{
					/*
					 * 由于值可能被多次读取，强制为只读
					 * - 但只有在它可能是扩展数据的情况下。
					 */
					if (get_typlen(fc_ctest->resulttype) == -1)
					{
						ExprEvalStep fc_scratch2 = {0};

						/* 是的，因此为 MAKE_READONLY 创建输出工作空间 */
						fc_domainval = (Datum *) palloc(sizeof(Datum));
						fc_domainnull = (bool *) palloc(sizeof(bool));

						/* 发出 MAKE_READONLY */
						fc_scratch2.opcode = EEOP_MAKE_READONLY;
						fc_scratch2.resvalue = fc_domainval;
						fc_scratch2.resnull = fc_domainnull;
						fc_scratch2.d.make_readonly.value = fc_resv;
						fc_scratch2.d.make_readonly.isnull = fc_resnull;
						ExprEvalPushStep(fc_state, &fc_scratch2);
					}
					else
					{
						/* 不，因此从 resv/resnull 中读取是可以的 */
						fc_domainval = fc_resv;
						fc_domainnull = fc_resnull;
					}
				}

				/*
				 * 设置 CoerceToDomainValue 节点返回的值。
				 * 我们必须保存和恢复 innermost_domainval/null 字段，
				 * 以防此节点本身位于另一个域的检查表达式中。
				 */
				fc_save_innermost_domainval = fc_state->innermost_domainval;
				fc_save_innermost_domainnull = fc_state->innermost_domainnull;
				fc_state->innermost_domainval = fc_domainval;
				fc_state->innermost_domainnull = fc_domainnull;

				/* 评估检查表达式值 */
				fc_ExecInitExprRec(fc_con->check_expr, fc_state,
								fc_scratch->d.domaincheck.checkvalue,
								fc_scratch->d.domaincheck.checknull);

				fc_state->innermost_domainval = fc_save_innermost_domainval;
				fc_state->innermost_domainnull = fc_save_innermost_domainnull;

				/* 现在测试结果 */
				fc_scratch->opcode = EEOP_DOMAIN_CHECK;
				ExprEvalPushStep(fc_state, fc_scratch);

				break;
			default:
				elog(ERROR, "unrecognized constraint type: %d",
					 (int) fc_con->constrainttype);
				break;
		}
	}
}

/*
 * 构建聚合过渡/组合函数调用的过渡函数，为所有聚合过渡
 * 组合函数调用在分组集阶段。这必须在一个阶段中调用所有基于排序的
 * 过渡（如果 doSort 为 true），所有基于哈希的过渡（如果 doHash 为 true），
 * 或两者（两者都为 true）。
 *
 * 生成的表达式将对每组过渡值首先检查过滤器，评估聚合输入，
 * 检查该输入对于严格过渡函数是否为 NULL，然后最后调用
 * 针对每个同时计算的分组集的过渡。
 *
 * 如果 nullcheck 为 true，生成的代码将检查指向 AggStatePerGroup 数组的 NULL 指针，
 * 如果是这样则跳过评估。
 */
ExprState * ExecBuildAggTrans(AggState *fc_aggstate, AggStatePerPhase fc_phase,
				  bool fc_doSort, bool fc_doHash, bool fc_nullcheck)
{
	ExprState  *fc_state = makeNode(ExprState);
	PlanState  *fc_parent = &fc_aggstate->ss.ps;
	ExprEvalStep fc_scratch = {0};
	bool		fc_isCombine = DO_AGGSPLIT_COMBINE(fc_aggstate->aggsplit);
	ExprSetupInfo fc_deform = {0, 0, 0, NIL};

	fc_state->expr = (Expr *) fc_aggstate;
	fc_state->parent = fc_parent;

	fc_scratch.resvalue = &fc_state->resvalue;
	fc_scratch.resnull = &fc_state->resnull;

	/*
	 * 首先确定我们需要的插槽和每个插槽需要的列数。
	 */
	for (int fc_transno = 0; fc_transno < fc_aggstate->numtrans; fc_transno++)
	{
		AggStatePerTrans fc_pertrans = &fc_aggstate->pertrans[fc_transno];

		fc_expr_setup_walker((Node *) fc_pertrans->aggref->aggdirectargs,
						  &fc_deform);
		fc_expr_setup_walker((Node *) fc_pertrans->aggref->args,
						  &fc_deform);
		fc_expr_setup_walker((Node *) fc_pertrans->aggref->aggorder,
						  &fc_deform);
		fc_expr_setup_walker((Node *) fc_pertrans->aggref->aggdistinct,
						  &fc_deform);
		fc_expr_setup_walker((Node *) fc_pertrans->aggref->aggfilter,
						  &fc_deform);
	}
	fc_ExecPushExprSetupSteps(fc_state, &fc_deform);

	
/*
	 * 为每个过渡值/分组集组合发出指令。
	 */
	for (int fc_transno = 0; fc_transno < fc_aggstate->numtrans; fc_transno++)
	{
		AggStatePerTrans fc_pertrans = &fc_aggstate->pertrans[fc_transno];
		FunctionCallInfo fc_trans_fcinfo = fc_pertrans->transfn_fcinfo;
		List	   *fc_adjust_bailout = NIL;
		NullableDatum *fc_strictargs = NULL;
		bool	   *fc_strictnulls = NULL;
		int			fc_argno;
		ListCell   *fc_bail;

		/*
		 * 如果存在过滤器，则发出。在评估输入之前进行，以
		 * 避免潜在的不必要计算，或者更糟糕的是，意外的
		 * 副作用。在组合时，所有必要的过滤已经
		 * 完成。
		 */
		if (fc_pertrans->aggref->aggfilter && !fc_isCombine)
		{
			/* 评估过滤表达式 */
			fc_ExecInitExprRec(fc_pertrans->aggref->aggfilter, fc_state,
							&fc_state->resvalue, &fc_state->resnull);
			/* 如果为假则跳出 */
			fc_scratch.opcode = EEOP_JUMP_IF_NOT_TRUE;
			fc_scratch.d.jump.jumpdone = -1;	/* 以后调整 */
			ExprEvalPushStep(fc_state, &fc_scratch);
			fc_adjust_bailout = lappend_int(fc_adjust_bailout,
										 fc_state->steps_len - 1);
		}

		/*
		 * 评估聚合/合并函数的参数。
		 */
		fc_argno = 0;
		if (fc_isCombine)
		{
			/*
			 * 组合两个聚合转换值。输入是一个可能被反序列化的
			 * 转换值，而不是直接来自元组。
			 */
			TargetEntry *fc_source_tle;

			Assert(fc_pertrans->numSortCols == 0);
			Assert(list_length(fc_pertrans->aggref->args) == 1);

			fc_strictargs = fc_trans_fcinfo->args + 1;
			fc_source_tle = (TargetEntry *) linitial(fc_pertrans->aggref->args);

			/*
			 * 如果必须在调用合并函数之前对输入状态进行反序列化，
			 * 将设置 deserialfn_oid。
			 */
			if (!OidIsValid(fc_pertrans->deserialfn_oid))
			{
				/*
				 * 从 1 开始，因为第 0 个参数将是转换值。
				 */
				fc_ExecInitExprRec(fc_source_tle->expr, fc_state,
								&fc_trans_fcinfo->args[fc_argno + 1].value,
								&fc_trans_fcinfo->args[fc_argno + 1].isnull);
			}
			else
			{
				FunctionCallInfo fc_ds_fcinfo = fc_pertrans->deserialfn_fcinfo;

				/* 评估参数 */
				fc_ExecInitExprRec(fc_source_tle->expr, fc_state,
								&fc_ds_fcinfo->args[0].value,
								&fc_ds_fcinfo->args[0].isnull);

				/* 出于类型安全原因的虚拟第二个参数 */
				fc_ds_fcinfo->args[1].value = PointerGetDatum(NULL);
				fc_ds_fcinfo->args[1].isnull = false;

				/*
				 * 不要用 NULL 输入调用严格的反序列化函数
				 */
				if (fc_pertrans->deserialfn.fn_strict)
					fc_scratch.opcode = EEOP_AGG_STRICT_DESERIALIZE;
				else
					fc_scratch.opcode = EEOP_AGG_DESERIALIZE;

				fc_scratch.d.agg_deserialize.fcinfo_data = fc_ds_fcinfo;
				fc_scratch.d.agg_deserialize.jumpnull = -1;	/* 以后调整 */
				fc_scratch.resvalue = &fc_trans_fcinfo->args[fc_argno + 1].value;
				fc_scratch.resnull = &fc_trans_fcinfo->args[fc_argno + 1].isnull;

				ExprEvalPushStep(fc_state, &fc_scratch);
				/* 除非函数是严格的，否则不添加调整 */
				if (fc_pertrans->deserialfn.fn_strict)
					fc_adjust_bailout = lappend_int(fc_adjust_bailout,
												 fc_state->steps_len - 1);

				/* 恢复临时字段的正常设置 */
				fc_scratch.resvalue = &fc_state->resvalue;
				fc_scratch.resnull = &fc_state->resnull;
			}
			fc_argno++;
		}
		else if (fc_pertrans->numSortCols == 0)
		{
			ListCell   *fc_arg;

			/*
			 * 普通转换函数，没有 ORDER BY / DISTINCT。
			 */
			fc_strictargs = fc_trans_fcinfo->args + 1;

			foreach(fc_arg, fc_pertrans->aggref->args)
			{
				TargetEntry *fc_source_tle = (TargetEntry *) lfirst(fc_arg);

				/*
				 * 从 1 开始，因为第 0 个参数将是转换值。
				 */
				fc_ExecInitExprRec(fc_source_tle->expr, fc_state,
								&fc_trans_fcinfo->args[fc_argno + 1].value,
								&fc_trans_fcinfo->args[fc_argno + 1].isnull);
				fc_argno++;
			}
		}
		else if (fc_pertrans->numInputs == 1)
		{
			/*
			 * DISTINCT 和/或 ORDER BY 情况，只对单列进行排序。
			 */
			TargetEntry *fc_source_tle =
			(TargetEntry *) linitial(fc_pertrans->aggref->args);

			Assert(list_length(fc_pertrans->aggref->args) == 1);

			fc_ExecInitExprRec(fc_source_tle->expr, fc_state,
							&fc_state->resvalue,
							&fc_state->resnull);
			fc_strictnulls = &fc_state->resnull;
			fc_argno++;
		}
		else
		{
			/*
			 * DISTINCT 和/或 ORDER BY 情况，对多个列进行排序。
			 */
			Datum	   *fc_values = fc_pertrans->sortslot->tts_values;
			bool	   *fc_nulls = fc_pertrans->sortslot->tts_isnull;
			ListCell   *fc_arg;

			fc_strictnulls = fc_nulls;

			foreach(fc_arg, fc_pertrans->aggref->args)
			{
				TargetEntry *fc_source_tle = (TargetEntry *) lfirst(fc_arg);

				fc_ExecInitExprRec(fc_source_tle->expr, fc_state,
								&fc_values[fc_argno], &fc_nulls[fc_argno]);
				fc_argno++;
			}
		}
		Assert(fc_pertrans->numInputs == fc_argno);

		/*
		 * 对于严格的 transfn，当输入为 NULL 时，不会发生任何事情；
		 * 我们只需保留先前的 transValue。这对普通和
		 * 排序/去重聚合都是如此。
		 */
		if (fc_trans_fcinfo->flinfo->fn_strict && fc_pertrans->numTransInputs > 0)
		{
			if (fc_strictnulls)
				fc_scratch.opcode = EEOP_AGG_STRICT_INPUT_CHECK_NULLS;
			else
				fc_scratch.opcode = EEOP_AGG_STRICT_INPUT_CHECK_ARGS;
			fc_scratch.d.agg_strict_input_check.nulls = fc_strictnulls;
			fc_scratch.d.agg_strict_input_check.args = fc_strictargs;
			fc_scratch.d.agg_strict_input_check.jumpnull = -1; /* 以后调整 */
			fc_scratch.d.agg_strict_input_check.nargs = fc_pertrans->numTransInputs;
			ExprEvalPushStep(fc_state, &fc_scratch);
			fc_adjust_bailout = lappend_int(fc_adjust_bailout,
										 fc_state->steps_len - 1);
		}

		/*
		 * 调用转换函数（对于每个同时评估的分组集一次）。对
		 * 排序和基于哈希的计算均如此，视情况而定。
		 */
		if (fc_doSort)
		{
			int			fc_processGroupingSets = Max(fc_phase->numsets, 1);
			int			fc_setoff = 0;

			for (int fc_setno = 0; fc_setno < fc_processGroupingSets; fc_setno++)
			{
				fc_ExecBuildAggTransCall(fc_state, fc_aggstate, &fc_scratch, fc_trans_fcinfo,
									  fc_pertrans, fc_transno, fc_setno, fc_setoff, false,
									  fc_nullcheck);
				fc_setoff++;
			}
		}

		if (fc_doHash)
		{
			int			fc_numHashes = fc_aggstate->num_hashes;
			int			fc_setoff;

			/* 在混合模式下，将有前置转换值 */
			if (fc_aggstate->aggstrategy != AGG_HASHED)
				fc_setoff = fc_aggstate->maxsets;
			else
				fc_setoff = 0;

			for (int fc_setno = 0; fc_setno < fc_numHashes; fc_setno++)
			{
				fc_ExecBuildAggTransCall(fc_state, fc_aggstate, &fc_scratch, fc_trans_fcinfo,
									  fc_pertrans, fc_transno, fc_setno, fc_setoff, true,
									  fc_nullcheck);
				fc_setoff++;
			}
		}

		/* 调整早期跳出跳转目标 */
		foreach(fc_bail, fc_adjust_bailout)
		{
			ExprEvalStep *fc_as = &fc_state->steps[lfirst_int(fc_bail)];

			if (fc_as->opcode == EEOP_JUMP_IF_NOT_TRUE)
			{
				Assert(fc_as->d.jump.jumpdone == -1);
				fc_as->d.jump.jumpdone = fc_state->steps_len;
			}
			else if (fc_as->opcode == EEOP_AGG_STRICT_INPUT_CHECK_ARGS ||
					 fc_as->opcode == EEOP_AGG_STRICT_INPUT_CHECK_NULLS)
			{
				Assert(fc_as->d.agg_strict_input_check.jumpnull == -1);
				fc_as->d.agg_strict_input_check.jumpnull = fc_state->steps_len;
			}
			else if (fc_as->opcode == EEOP_AGG_STRICT_DESERIALIZE)
			{
				Assert(fc_as->d.agg_deserialize.jumpnull == -1);
				fc_as->d.agg_deserialize.jumpnull = fc_state->steps_len;
			}
			else
				Assert(false);
		}
	}

	fc_scratch.resvalue = NULL;
	fc_scratch.resnull = NULL;
	fc_scratch.opcode = EEOP_DONE;
	ExprEvalPushStep(fc_state, &fc_scratch);

	fc_ExecReadyExpr(fc_state);

	return fc_state;
}

/*
 * 为单个转换值构建转换/合并函数调用。因为有多个调用点
 * （某些分组集情况下的哈希和排序），所以与 ExecBuildAggTrans() 
 * 分开。
 */
static void fc_ExecBuildAggTransCall(ExprState *fc_state, AggState *fc_aggstate,
					  ExprEvalStep *fc_scratch,
					  FunctionCallInfo fcinfo, AggStatePerTrans fc_pertrans,
					  int fc_transno, int fc_setno, int fc_setoff, bool fc_ishash,
					  bool fc_nullcheck)
{
	ExprContext *fc_aggcontext;
	int			fc_adjust_jumpnull = -1;

	if (fc_ishash)
		fc_aggcontext = fc_aggstate->hashcontext;
	else
		fc_aggcontext = fc_aggstate->aggcontexts[fc_setno];

	/* 添加对 NULL 指针的检查？ */
	if (fc_nullcheck)
	{
		fc_scratch->opcode = EEOP_AGG_PLAIN_PERGROUP_NULLCHECK;
		fc_scratch->d.agg_plain_pergroup_nullcheck.setoff = fc_setoff;
		/* 以后调整 */
		fc_scratch->d.agg_plain_pergroup_nullcheck.jumpnull = -1;
		ExprEvalPushStep(fc_state, fc_scratch);
		fc_adjust_jumpnull = fc_state->steps_len - 1;
	}

	/*
	 * 确定适当的转换实现。
	 *
	 * 对于非有序聚合：
	 *
	 * 如果转换状态的初始值在 pg_aggregate 表中不存在，
	 * 那么我们将让从外部 procNode 返回的第一个非 NULL 值成为初始值。
	 * （这对于像 max() 和 min() 这样的聚合很有用。）noTransValue 标志指示
	 * 我们需要这样做。如果为真，生成一个
	 * EEOP_AGG_INIT_STRICT_PLAIN_TRANS{,_BYVAL} 步骤。这个步骤还需要
	 * 做后面的工作：
	 *
	 * 如果函数是严格的，但有初始值，选择
	 * EEOP_AGG_STRICT_PLAIN_TRANS{,_BYVAL}，如果转换值变为 NULL
	 * （因为先前的转换函数返回了 NULL），则跳过转换函数。这个步骤也
	 * 需要做后面的工作：
	 *
	 * 否则我们调用 EEOP_AGG_PLAIN_TRANS{,_BYVAL}，它不需要执行上述任
	 * 何检查。
	 *
	 * 拥有 overlapping responsibilities 的步骤并不好，但
	 * 聚合对性能很敏感，使得这样值得。
	 *
	 * 对于有序聚合：
	 *
	 * 只需在单列和多列之间选择较快的路径。严格性等的检查
	 * 在最终确定聚合时进行。参见
	 * process_ordered_aggregate_{single, multi} 和
	 * advance_transition_function。
	 */
	if (fc_pertrans->numSortCols == 0)
	{
		if (fc_pertrans->transtypeByVal)
		{
			if (fcinfo->flinfo->fn_strict &&
				fc_pertrans->initValueIsNull)
				fc_scratch->opcode = EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL;
			else if (fcinfo->flinfo->fn_strict)
				fc_scratch->opcode = EEOP_AGG_PLAIN_TRANS_STRICT_BYVAL;
			else
				fc_scratch->opcode = EEOP_AGG_PLAIN_TRANS_BYVAL;
		}
		else
		{
			if (fcinfo->flinfo->fn_strict &&
				fc_pertrans->initValueIsNull)
				fc_scratch->opcode = EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYREF;
			else if (fcinfo->flinfo->fn_strict)
				fc_scratch->opcode = EEOP_AGG_PLAIN_TRANS_STRICT_BYREF;
			else
				fc_scratch->opcode = EEOP_AGG_PLAIN_TRANS_BYREF;
		}
	}
	else if (fc_pertrans->numInputs == 1)
		fc_scratch->opcode = EEOP_AGG_ORDERED_TRANS_DATUM;
	else
		fc_scratch->opcode = EEOP_AGG_ORDERED_TRANS_TUPLE;

	fc_scratch->d.agg_trans.pertrans = fc_pertrans;
	fc_scratch->d.agg_trans.setno = fc_setno;
	fc_scratch->d.agg_trans.setoff = fc_setoff;
	fc_scratch->d.agg_trans.transno = fc_transno;
	fc_scratch->d.agg_trans.aggcontext = fc_aggcontext;
	ExprEvalPushStep(fc_state, fc_scratch);

	/* 修复跳转空值 */
	if (fc_adjust_jumpnull != -1)
	{
		ExprEvalStep *fc_as = &fc_state->steps[fc_adjust_jumpnull];

		Assert(fc_as->opcode == EEOP_AGG_PLAIN_PERGROUP_NULLCHECK);
		Assert(fc_as->d.agg_plain_pergroup_nullcheck.jumpnull == -1);
		fc_as->d.agg_plain_pergroup_nullcheck.jumpnull = fc_state->steps_len;
	}
}

/*
 * 构建可以使用 ExecQual() 进行评估的相等表达式，如果表达式上下文
 * 的内/外元组不相等则返回真。即两个空值匹配，一个空值和一个非空值不匹配。
 *
 * desc: 要比较的元组的元组描述符
 * numCols: 要检查的属性数量
 * keyColIdx: 属性列号的数组
 * eqFunctions: 要使用的相等函数的 oid 数组
 * parent: 父执行节点
 */
ExprState * ExecBuildGroupingEqual(TupleDesc fc_ldesc, TupleDesc fc_rdesc,
					   const TupleTableSlotOps *fc_lops, const TupleTableSlotOps *fc_rops,
					   int fc_numCols,
					   const AttrNumber *fc_keyColIdx,
					   const Oid *fc_eqfunctions,
					   const Oid *fc_collations,
					   PlanState *fc_parent)
{
	ExprState  *fc_state = makeNode(ExprState);
	ExprEvalStep fc_scratch = {0};
	int			fc_maxatt = -1;
	List	   *fc_adjust_jumps = NIL;
	ListCell   *fc_lc;

	/*
	 * 当没有实际比较的列时，结果总是为真。见 ExecQual() 中的特殊情况。
	 */
	if (fc_numCols == 0)
		return NULL;

	fc_state->expr = NULL;
	fc_state->flags = EEO_FLAG_IS_QUAL;
	fc_state->parent = fc_parent;

	fc_scratch.resvalue = &fc_state->resvalue;
	fc_scratch.resnull = &fc_state->resnull;

	/* 计算所需的最大属性 */
	for (int fc_natt = 0; fc_natt < fc_numCols; fc_natt++)
	{
		int			fc_attno = fc_keyColIdx[fc_natt];

		if (fc_attno > fc_maxatt)
			fc_maxatt = fc_attno;
	}
	Assert(fc_maxatt >= 0);

	/* 推送变形步骤 */
	fc_scratch.opcode = EEOP_INNER_FETCHSOME;
	fc_scratch.d.fetch.last_var = fc_maxatt;
	fc_scratch.d.fetch.fixed = false;
	fc_scratch.d.fetch.known_desc = fc_ldesc;
	fc_scratch.d.fetch.kind = fc_lops;
	if (fc_ExecComputeSlotInfo(fc_state, &fc_scratch))
		ExprEvalPushStep(fc_state, &fc_scratch);

	fc_scratch.opcode = EEOP_OUTER_FETCHSOME;
	fc_scratch.d.fetch.last_var = fc_maxatt;
	fc_scratch.d.fetch.fixed = false;
	fc_scratch.d.fetch.known_desc = fc_rdesc;
	fc_scratch.d.fetch.kind = fc_rops;
	if (fc_ExecComputeSlotInfo(fc_state, &fc_scratch))
		ExprEvalPushStep(fc_state, &fc_scratch);

	/*
	 * 从最后一个字段（最低有效排序键）开始比较。如果我们处理的是排序过的输入，
	 * 那么这最有可能不同。
	 */
	for (int fc_natt = fc_numCols; --fc_natt >= 0;)
	{
		int			fc_attno = fc_keyColIdx[fc_natt];
		Form_pg_attribute fc_latt = TupleDescAttr(fc_ldesc, fc_attno - 1);
		Form_pg_attribute fc_ratt = TupleDescAttr(fc_rdesc, fc_attno - 1);
		Oid			fc_foid = fc_eqfunctions[fc_natt];
		Oid			fc_collid = fc_collations[fc_natt];
		FmgrInfo   *fc_finfo;
		FunctionCallInfo fcinfo;
		AclResult	fc_aclresult;

		/* 检查调用函数的权限 */
		fc_aclresult = pg_proc_aclcheck(fc_foid, GetUserId(), ACL_EXECUTE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_FUNCTION, get_func_name(fc_foid));

		InvokeFunctionExecuteHook(fc_foid);

		/* 设置主fmgr查找信息 */
		fc_finfo = palloc0(sizeof(FmgrInfo));
		fcinfo = palloc0(SizeForFunctionCallInfo(2));
		fmgr_info(fc_foid, fc_finfo);
		fmgr_info_set_expr(NULL, fc_finfo);
		InitFunctionCallInfoData(*fcinfo, fc_finfo, 2,
								 fc_collid, NULL, NULL);

		/* left arg */
		fc_scratch.opcode = EEOP_INNER_VAR;
		fc_scratch.d.var.attnum = fc_attno - 1;
		fc_scratch.d.var.vartype = fc_latt->atttypid;
		fc_scratch.resvalue = &fcinfo->args[0].value;
		fc_scratch.resnull = &fcinfo->args[0].isnull;
		ExprEvalPushStep(fc_state, &fc_scratch);

		/* 右边的参数 */
		fc_scratch.opcode = EEOP_OUTER_VAR;
		fc_scratch.d.var.attnum = fc_attno - 1;
		fc_scratch.d.var.vartype = fc_ratt->atttypid;
		fc_scratch.resvalue = &fcinfo->args[1].value;
		fc_scratch.resnull = &fcinfo->args[1].isnull;
		ExprEvalPushStep(fc_state, &fc_scratch);

		/* 评估不同性 */
		fc_scratch.opcode = EEOP_NOT_DISTINCT;
		fc_scratch.d.func.finfo = fc_finfo;
		fc_scratch.d.func.fcinfo_data = fcinfo;
		fc_scratch.d.func.fn_addr = fc_finfo->fn_addr;
		fc_scratch.d.func.nargs = 2;
		fc_scratch.resvalue = &fc_state->resvalue;
		fc_scratch.resnull = &fc_state->resnull;
		ExprEvalPushStep(fc_state, &fc_scratch);

		/* 然后发出 EEOP_QUAL 以检测结果是否为假（或 null） */
		fc_scratch.opcode = EEOP_QUAL;
		fc_scratch.d.qualexpr.jumpdone = -1;
		fc_scratch.resvalue = &fc_state->resvalue;
		fc_scratch.resnull = &fc_state->resnull;
		ExprEvalPushStep(fc_state, &fc_scratch);
		fc_adjust_jumps = lappend_int(fc_adjust_jumps,
								   fc_state->steps_len - 1);
	}

	/* 调整跳转目标 */
	foreach(fc_lc, fc_adjust_jumps)
	{
		ExprEvalStep *fc_as = &fc_state->steps[lfirst_int(fc_lc)];

		Assert(fc_as->opcode == EEOP_QUAL);
		Assert(fc_as->d.qualexpr.jumpdone == -1);
		fc_as->d.qualexpr.jumpdone = fc_state->steps_len;
	}

	fc_scratch.resvalue = NULL;
	fc_scratch.resnull = NULL;
	fc_scratch.opcode = EEOP_DONE;
	ExprEvalPushStep(fc_state, &fc_scratch);

	fc_ExecReadyExpr(fc_state);

	return fc_state;
}


/*
 * 构建可以通过 ExecQual() 评估的相等表达式，返回
 * 如果表达式上下文的内/外元组相等则返回 true。假定内/外槽中的 Datum
 * 与 'eqfunctions' 参数的顺序和数量相同。NULL 被视为相等。
 *
 * desc: 待比较元组的元组描述符
 * lops: 内元组槽的槽操作
 * rops: 外元组槽的槽操作
 * eqFunctions: 用于相等函数的函数 oid 数组
 * 这必须与 'param_exprs' 列表的长度相同。
 * collations: 用于相等比较的排序 Oid。必须与 'param_exprs' 列表的长度相同。
 * parent: 父执行器节点
 */
ExprState * ExecBuildParamSetEqual(TupleDesc fc_desc,
					   const TupleTableSlotOps *fc_lops,
					   const TupleTableSlotOps *fc_rops,
					   const Oid *fc_eqfunctions,
					   const Oid *fc_collations,
					   const List *fc_param_exprs,
					   PlanState *fc_parent)
{
	ExprState  *fc_state = makeNode(ExprState);
	ExprEvalStep fc_scratch = {0};
	int			fc_maxatt = list_length(fc_param_exprs);
	List	   *fc_adjust_jumps = NIL;
	ListCell   *fc_lc;

	fc_state->expr = NULL;
	fc_state->flags = EEO_FLAG_IS_QUAL;
	fc_state->parent = fc_parent;

	fc_scratch.resvalue = &fc_state->resvalue;
	fc_scratch.resnull = &fc_state->resnull;

	/* 推送变形步骤 */
	fc_scratch.opcode = EEOP_INNER_FETCHSOME;
	fc_scratch.d.fetch.last_var = fc_maxatt;
	fc_scratch.d.fetch.fixed = false;
	fc_scratch.d.fetch.known_desc = fc_desc;
	fc_scratch.d.fetch.kind = fc_lops;
	if (fc_ExecComputeSlotInfo(fc_state, &fc_scratch))
		ExprEvalPushStep(fc_state, &fc_scratch);

	fc_scratch.opcode = EEOP_OUTER_FETCHSOME;
	fc_scratch.d.fetch.last_var = fc_maxatt;
	fc_scratch.d.fetch.fixed = false;
	fc_scratch.d.fetch.known_desc = fc_desc;
	fc_scratch.d.fetch.kind = fc_rops;
	if (fc_ExecComputeSlotInfo(fc_state, &fc_scratch))
		ExprEvalPushStep(fc_state, &fc_scratch);

	for (int fc_attno = 0; fc_attno < fc_maxatt; fc_attno++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_desc, fc_attno);
		Oid			fc_foid = fc_eqfunctions[fc_attno];
		Oid			fc_collid = fc_collations[fc_attno];
		FmgrInfo   *fc_finfo;
		FunctionCallInfo fcinfo;
		AclResult	fc_aclresult;

		/* 检查调用函数的权限 */
		fc_aclresult = pg_proc_aclcheck(fc_foid, GetUserId(), ACL_EXECUTE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_FUNCTION, get_func_name(fc_foid));

		InvokeFunctionExecuteHook(fc_foid);

		/* 设置主fmgr查找信息 */
		fc_finfo = palloc0(sizeof(FmgrInfo));
		fcinfo = palloc0(SizeForFunctionCallInfo(2));
		fmgr_info(fc_foid, fc_finfo);
		fmgr_info_set_expr(NULL, fc_finfo);
		InitFunctionCallInfoData(*fcinfo, fc_finfo, 2,
								 fc_collid, NULL, NULL);

		/* left arg */
		fc_scratch.opcode = EEOP_INNER_VAR;
		fc_scratch.d.var.attnum = fc_attno;
		fc_scratch.d.var.vartype = fc_att->atttypid;
		fc_scratch.resvalue = &fcinfo->args[0].value;
		fc_scratch.resnull = &fcinfo->args[0].isnull;
		ExprEvalPushStep(fc_state, &fc_scratch);

		/* 右边的参数 */
		fc_scratch.opcode = EEOP_OUTER_VAR;
		fc_scratch.d.var.attnum = fc_attno;
		fc_scratch.d.var.vartype = fc_att->atttypid;
		fc_scratch.resvalue = &fcinfo->args[1].value;
		fc_scratch.resnull = &fcinfo->args[1].isnull;
		ExprEvalPushStep(fc_state, &fc_scratch);

		/* 评估不同性 */
		fc_scratch.opcode = EEOP_NOT_DISTINCT;
		fc_scratch.d.func.finfo = fc_finfo;
		fc_scratch.d.func.fcinfo_data = fcinfo;
		fc_scratch.d.func.fn_addr = fc_finfo->fn_addr;
		fc_scratch.d.func.nargs = 2;
		fc_scratch.resvalue = &fc_state->resvalue;
		fc_scratch.resnull = &fc_state->resnull;
		ExprEvalPushStep(fc_state, &fc_scratch);

		/* 然后发出 EEOP_QUAL 以检测结果是否为假（或 null） */
		fc_scratch.opcode = EEOP_QUAL;
		fc_scratch.d.qualexpr.jumpdone = -1;
		fc_scratch.resvalue = &fc_state->resvalue;
		fc_scratch.resnull = &fc_state->resnull;
		ExprEvalPushStep(fc_state, &fc_scratch);
		fc_adjust_jumps = lappend_int(fc_adjust_jumps,
								   fc_state->steps_len - 1);
	}

	/* 调整跳转目标 */
	foreach(fc_lc, fc_adjust_jumps)
	{
		ExprEvalStep *fc_as = &fc_state->steps[lfirst_int(fc_lc)];

		Assert(fc_as->opcode == EEOP_QUAL);
		Assert(fc_as->d.qualexpr.jumpdone == -1);
		fc_as->d.qualexpr.jumpdone = fc_state->steps_len;
	}

	fc_scratch.resvalue = NULL;
	fc_scratch.resnull = NULL;
	fc_scratch.opcode = EEOP_DONE;
	ExprEvalPushStep(fc_state, &fc_scratch);

	fc_ExecReadyExpr(fc_state);

	return fc_state;
}
